diff options
Diffstat (limited to '')
64 files changed, 78742 insertions, 0 deletions
diff --git a/source4/torture/rpc/alter_context.c b/source4/torture/rpc/alter_context.c new file mode 100644 index 0000000..22abd97 --- /dev/null +++ b/source4/torture/rpc/alter_context.c @@ -0,0 +1,112 @@ +/* + Unix SMB/CIFS implementation. + + test suite for dcerpc alter_context operations + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_dssetup.h" +#include "torture/rpc/torture_rpc.h" + +bool torture_rpc_alter_context(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p, *p2, *p3; + struct policy_handle *handle; + struct ndr_interface_table tmptbl; + bool ret = true; + + torture_comment(torture, "opening LSA connection\n"); + status = torture_rpc_connection(torture, &p, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(torture, status, "connecting"); + + torture_comment(torture, "Testing change of primary context\n"); + status = dcerpc_alter_context(p, torture, &p->syntax, &p->transfer_syntax); + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + if (!test_lsa_OpenPolicy2(p->binding_handle, torture, &handle)) { + ret = false; + } + + torture_comment(torture, "Testing change of primary context\n"); + status = dcerpc_alter_context(p, torture, &p->syntax, &p->transfer_syntax); + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + torture_comment(torture, "Opening secondary DSSETUP context\n"); + status = dcerpc_secondary_context(p, &p2, &ndr_table_dssetup); + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + torture_comment(torture, "Testing change of primary context\n"); + status = dcerpc_alter_context(p2, torture, &p2->syntax, &p2->transfer_syntax); + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + tmptbl = ndr_table_dssetup; + tmptbl.syntax_id.if_version += 100; + torture_comment(torture, "Opening bad secondary connection\n"); + status = dcerpc_secondary_context(p, &p3, &tmptbl); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX, + "dcerpc_alter_context with wrong version should fail"); + + torture_comment(torture, "Testing DSSETUP pipe operations\n"); + ret &= test_DsRoleGetPrimaryDomainInformation(torture, p2); + + if (handle) { + ret &= test_lsa_Close(p->binding_handle, torture, handle); + } + + torture_comment(torture, "Testing change of primary context\n"); + status = dcerpc_alter_context(p, torture, &p->syntax, &p->transfer_syntax); + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + ret &= test_lsa_OpenPolicy2(p->binding_handle, torture, &handle); + + if (handle) { + ret &= test_lsa_Close(p->binding_handle, torture, handle); + } + + torture_comment(torture, "Testing change of primary context\n"); + status = dcerpc_alter_context(p, torture, &p2->syntax, &p2->transfer_syntax); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + + ret &= test_lsa_OpenPolicy2_ex(p->binding_handle, torture, &handle, + NT_STATUS_CONNECTION_DISCONNECTED, + NT_STATUS_CONNECTION_RESET); + + torture_assert(torture, !dcerpc_binding_handle_is_connected(p->binding_handle), + "dcerpc disonnected"); + + return ret; + } + torture_assert_ntstatus_ok(torture, status, "dcerpc_alter_context failed"); + + torture_comment(torture, "Testing DSSETUP pipe operations - should fault\n"); + ret &= test_DsRoleGetPrimaryDomainInformation_ext(torture, p, NT_STATUS_RPC_BAD_STUB_DATA); + + ret &= test_lsa_OpenPolicy2(p->binding_handle, torture, &handle); + + if (handle) { + ret &= test_lsa_Close(p->binding_handle, torture, handle); + } + + torture_comment(torture, "Testing DSSETUP pipe operations\n"); + + ret &= test_DsRoleGetPrimaryDomainInformation(torture, p2); + + return ret; +} diff --git a/source4/torture/rpc/async_bind.c b/source4/torture/rpc/async_bind.c new file mode 100644 index 0000000..e86d0ab --- /dev/null +++ b/source4/torture/rpc/async_bind.c @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc torture tests + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Rafal Szczesniak 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_lsa.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" + +/* + This test initiates multiple rpc bind requests and verifies + whether all of them are served. +*/ + + +bool torture_async_bind(struct torture_context *torture) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx; + int i; + const char *binding_string; + struct cli_credentials *creds; + extern int torture_numasync; + + struct composite_context **bind_req; + struct dcerpc_pipe **pipes; + const struct ndr_interface_table **table; + + if (!torture_setting_bool(torture, "async", false)) { + printf("async bind test disabled - enable async tests to use\n"); + return true; + } + + binding_string = torture_setting_string(torture, "binding", NULL); + + /* talloc context */ + mem_ctx = talloc_init("torture_async_bind"); + if (mem_ctx == NULL) return false; + + bind_req = talloc_array(torture, struct composite_context*, torture_numasync); + if (bind_req == NULL) return false; + pipes = talloc_array(torture, struct dcerpc_pipe*, torture_numasync); + if (pipes == NULL) return false; + table = talloc_array(torture, const struct ndr_interface_table*, torture_numasync); + if (table == NULL) return false; + + /* credentials */ + creds = samba_cmdline_get_creds(); + + /* send bind requests */ + for (i = 0; i < torture_numasync; i++) { + table[i] = &ndr_table_lsarpc; + bind_req[i] = dcerpc_pipe_connect_send(mem_ctx, binding_string, + table[i], creds, torture->ev, torture->lp_ctx); + } + + /* recv bind requests */ + for (i = 0; i < torture_numasync; i++) { + status = dcerpc_pipe_connect_recv(bind_req[i], mem_ctx, &pipes[i]); + if (!NT_STATUS_IS_OK(status)) { + printf("async rpc connection failed: %s\n", nt_errstr(status)); + return false; + } + } + + talloc_free(mem_ctx); + return true; +} diff --git a/source4/torture/rpc/atsvc.c b/source4/torture/rpc/atsvc.c new file mode 100644 index 0000000..729a7ca --- /dev/null +++ b/source4/torture/rpc/atsvc.c @@ -0,0 +1,138 @@ +/* + Unix SMB/CIFS implementation. + test suite for atsvc rpc operations + + Copyright (C) Tim Potter 2003 + + 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_atsvc_c.h" +#include "torture/rpc/torture_rpc.h" + +static bool test_JobGetInfo(struct dcerpc_pipe *p, struct torture_context *tctx, uint32_t job_id) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct atsvc_JobGetInfo r; + struct atsvc_JobInfo *info = talloc(tctx, struct atsvc_JobInfo); + if (!info) { + return false; + } + + r.in.servername = dcerpc_server_name(p); + r.in.job_id = job_id; + r.out.job_info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_atsvc_JobGetInfo_r(b, tctx, &r), + "JobGetInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "JobGetInfo failed"); + + return true; +} + +static bool test_JobDel(struct dcerpc_pipe *p, struct torture_context *tctx, uint32_t min_job_id, + uint32_t max_job_id) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct atsvc_JobDel r; + + r.in.servername = dcerpc_server_name(p); + r.in.min_job_id = min_job_id; + r.in.max_job_id = max_job_id; + + torture_assert_ntstatus_ok(tctx, dcerpc_atsvc_JobDel_r(b, tctx, &r), + "JobDel failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "JobDel failed"); + + return true; +} + +static bool test_JobEnum(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct atsvc_JobEnum r; + struct atsvc_enum_ctr ctr; + uint32_t resume_handle = 0, i, total_entries = 0; + + bool ret = true; + + r.in.servername = dcerpc_server_name(p); + ctr.entries_read = 0; + ctr.first_entry = NULL; + r.in.ctr = r.out.ctr = &ctr; + r.in.preferred_max_len = 0xffffffff; + r.in.resume_handle = r.out.resume_handle = &resume_handle; + r.out.total_entries = &total_entries; + + torture_assert_ntstatus_ok(tctx, dcerpc_atsvc_JobEnum_r(b, tctx, &r), + "JobEnum failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "JobEnum failed"); + + for (i = 0; i < r.out.ctr->entries_read; i++) { + if (!test_JobGetInfo(p, tctx, r.out.ctr->first_entry[i].job_id)) { + ret = false; + } + } + + return ret; +} + +static bool test_JobAdd(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct atsvc_JobAdd r; + struct atsvc_JobInfo info; + + r.in.servername = dcerpc_server_name(p); + info.job_time = 0x050ae4c0; /* 11:30pm */ + info.days_of_month = 0; /* n/a */ + info.days_of_week = 0x02; /* Tuesday */ + info.flags = 0x11; /* periodic, non-interactive */ + info.command = "foo.exe"; + r.in.job_info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_atsvc_JobAdd_r(b, tctx, &r), + "JobAdd failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "JobAdd failed"); + + /* Run EnumJobs again in case there were no jobs to begin with */ + + if (!test_JobEnum(tctx, p)) { + return false; + } + + if (!test_JobGetInfo(p, tctx, *r.out.job_id)) { + return false; + } + + if (!test_JobDel(p, tctx, *r.out.job_id, *r.out.job_id)) { + return false; + } + + return true; +} + +struct torture_suite *torture_rpc_atsvc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "atsvc"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "atsvc", &ndr_table_atsvc); + + torture_rpc_tcase_add_test(tcase, "JobEnum", test_JobEnum); + torture_rpc_tcase_add_test(tcase, "JobAdd", test_JobAdd); + + return suite; +} diff --git a/source4/torture/rpc/backupkey.c b/source4/torture/rpc/backupkey.c new file mode 100644 index 0000000..c84e628 --- /dev/null +++ b/source4/torture/rpc/backupkey.c @@ -0,0 +1,2390 @@ +/* + Unix SMB/CIFS implementation. + test suite for backupkey remote protocol rpc operations + + Copyright (C) Matthieu Patou 2010-2011 + 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 "../libcli/security/security.h" + +#include "torture/rpc/torture_rpc.h" +#include "torture/ndr/ndr.h" + +#include "librpc/gen_ndr/ndr_backupkey_c.h" +#include "librpc/gen_ndr/ndr_backupkey.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "lib/cmdline/cmdline.h" +#include "libcli/auth/proto.h" +#include <system/network.h> + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> +#include <gnutls/abstract.h> + +enum test_wrong { + WRONG_MAGIC, + WRONG_R2, + WRONG_PAYLOAD_LENGTH, + WRONG_CIPHERTEXT_LENGTH, + SHORT_PAYLOAD_LENGTH, + SHORT_CIPHERTEXT_LENGTH, + ZERO_PAYLOAD_LENGTH, + ZERO_CIPHERTEXT_LENGTH, + RIGHT_KEY, + WRONG_KEY, + WRONG_SID, +}; + +/* Our very special and valued secret */ +/* No need to put const as we cast the array in uint8_t + * we will get a warning about the discared const + */ +static const char secret[] = "tata yoyo mais qu'est ce qu'il y a sous ton grand chapeau ?"; + +/* Get the SID from a user */ +static struct dom_sid *get_user_sid(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + const char *user) +{ + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + struct lsa_Close c; + NTSTATUS status; + struct policy_handle handle; + struct lsa_LookupNames l; + struct lsa_TransSidArray sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String lsa_name; + uint32_t count = 0; + struct dom_sid *result; + TALLOC_CTX *tmp_ctx; + struct dcerpc_pipe *p2; + struct dcerpc_binding_handle *b; + + const char *domain = cli_credentials_get_domain( + samba_cmdline_get_creds()); + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_lsarpc), + "could not open lsarpc pipe"); + b = p2->binding_handle; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + return NULL; + } + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy2_r(b, tmp_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, + "OpenPolicy2 failed - %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, + "OpenPolicy2_ failed - %s\n", + nt_errstr(r.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + sids.count = 0; + sids.sids = NULL; + + lsa_name.string = talloc_asprintf(tmp_ctx, "%s\\%s", domain, user); + + l.in.handle = &handle; + l.in.num_names = 1; + l.in.names = &lsa_name; + l.in.sids = &sids; + l.in.level = 1; + l.in.count = &count; + l.out.count = &count; + l.out.sids = &sids; + l.out.domains = &domains; + + status = dcerpc_lsa_LookupNames_r(b, tmp_ctx, &l); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, + "LookupNames of %s failed - %s\n", + lsa_name.string, + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + + if (domains->count == 0) { + return NULL; + } + + result = dom_sid_add_rid(mem_ctx, + domains->domains[0].sid, + l.out.sids->sids[0].rid); + c.in.handle = &handle; + c.out.handle = &handle; + + status = dcerpc_lsa_Close_r(b, tmp_ctx, &c); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, + "dcerpc_lsa_Close failed - %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + + if (!NT_STATUS_IS_OK(c.out.result)) { + torture_comment(tctx, + "dcerpc_lsa_Close failed - %s\n", + nt_errstr(c.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + talloc_free(tmp_ctx); + talloc_free(p2); + + torture_comment(tctx, "Get_user_sid finished\n"); + return result; +} + +/* + * Create a bkrp_encrypted_secret_vX structure + * the version depends on the version parameter + * the structure is returned as a blob. + * The broken flag is to indicate if we want + * to create a non conform to specification structre + */ +static DATA_BLOB *create_unencryptedsecret(TALLOC_CTX *mem_ctx, + bool broken, + int version) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + DATA_BLOB *blob = talloc_zero(mem_ctx, DATA_BLOB); + enum ndr_err_code ndr_err; + + if (version == 2) { + struct bkrp_encrypted_secret_v2 unenc_sec; + + ZERO_STRUCT(unenc_sec); + unenc_sec.secret_len = sizeof(secret); + unenc_sec.secret = discard_const_p(uint8_t, secret); + generate_random_buffer(unenc_sec.payload_key, + sizeof(unenc_sec.payload_key)); + + ndr_err = ndr_push_struct_blob(blob, blob, &unenc_sec, + (ndr_push_flags_fn_t)ndr_push_bkrp_encrypted_secret_v2); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + + if (broken) { + /* The magic value is correctly set by the NDR push + * but we want to test the behavior of the server + * if a differrent value is provided + */ + ((uint8_t*)blob->data)[4] = 79; /* A great year !!! */ + } + } + + if (version == 3) { + struct bkrp_encrypted_secret_v3 unenc_sec; + + ZERO_STRUCT(unenc_sec); + unenc_sec.secret_len = sizeof(secret); + unenc_sec.secret = discard_const_p(uint8_t, secret); + generate_random_buffer(unenc_sec.payload_key, + sizeof(unenc_sec.payload_key)); + + ndr_err = ndr_push_struct_blob(blob, blob, &unenc_sec, + (ndr_push_flags_fn_t)ndr_push_bkrp_encrypted_secret_v3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + + if (broken) { + /* + * The magic value is correctly set by the NDR push + * but we want to test the behavior of the server + * if a differrent value is provided + */ + ((uint8_t*)blob->data)[4] = 79; /* A great year !!! */ + } + } + talloc_free(tmp_ctx); + return blob; +} + +/* + * Create an access check structure, the format depends on the version parameter. + * If broken is specified then we create a stucture that isn't conform to the + * specification. + * + * If the structure can't be created then NULL is returned. + */ +static DATA_BLOB *create_access_check(struct torture_context *tctx, + struct dcerpc_pipe *p, + TALLOC_CTX *mem_ctx, + const char *user, + bool broken, + uint32_t version) +{ + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + DATA_BLOB *blob = talloc_zero(mem_ctx, DATA_BLOB); + enum ndr_err_code ndr_err; + const struct dom_sid *sid = get_user_sid(tctx, tmp_ctx, user); + + if (sid == NULL) { + return NULL; + } + + if (version == 2) { + struct bkrp_access_check_v2 access_struct; + gnutls_hash_hd_t dig_ctx; + uint8_t nonce[32]; + + ZERO_STRUCT(access_struct); + generate_random_buffer(nonce, sizeof(nonce)); + access_struct.nonce_len = sizeof(nonce); + access_struct.nonce = nonce; + access_struct.sid = *sid; + + ndr_err = ndr_push_struct_blob(blob, blob, &access_struct, + (ndr_push_flags_fn_t)ndr_push_bkrp_access_check_v2); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + + /* + * We pushed the whole structure including a null hash + * but the hash need to be calculated only up to the hash field + * so we reduce the size of what has to be calculated + */ + + gnutls_hash_init(&dig_ctx, GNUTLS_DIG_SHA1); + gnutls_hash(dig_ctx, + blob->data, + blob->length - sizeof(access_struct.hash)); + gnutls_hash_deinit(dig_ctx, + blob->data + blob->length - sizeof(access_struct.hash)); + + /* Altering the SHA */ + if (broken) { + blob->data[blob->length - 1]++; + } + } + + if (version == 3) { + struct bkrp_access_check_v3 access_struct; + gnutls_hash_hd_t dig_ctx; + uint8_t nonce[32]; + + ZERO_STRUCT(access_struct); + generate_random_buffer(nonce, sizeof(nonce)); + access_struct.nonce_len = sizeof(nonce); + access_struct.nonce = nonce; + access_struct.sid = *sid; + + ndr_err = ndr_push_struct_blob(blob, blob, &access_struct, + (ndr_push_flags_fn_t)ndr_push_bkrp_access_check_v3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + + /*We pushed the whole structure including a null hash + * but the hash need to be calculated only up to the hash field + * so we reduce the size of what has to be calculated + */ + + gnutls_hash_init(&dig_ctx, GNUTLS_DIG_SHA512); + gnutls_hash(dig_ctx, + blob->data, + blob->length - sizeof(access_struct.hash)); + gnutls_hash_deinit(dig_ctx, + blob->data + blob->length - sizeof(access_struct.hash)); + + /* Altering the SHA */ + if (broken) { + blob->data[blob->length -1]++; + } + } + talloc_free(tmp_ctx); + return blob; +} + + +static DATA_BLOB *encrypt_blob(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + DATA_BLOB *key, + DATA_BLOB *iv, + DATA_BLOB *to_encrypt, + gnutls_cipher_algorithm_t cipher_algo) +{ + gnutls_cipher_hd_t cipher_handle = { 0 }; + gnutls_datum_t gkey = { + .data = key->data, + .size = key->length, + }; + gnutls_datum_t giv = { + .data = iv->data, + .size = iv->length, + }; + DATA_BLOB *blob; + int rc; + + blob = talloc(mem_ctx, DATA_BLOB); + if (blob == NULL) { + return NULL; + } + + *blob = data_blob_talloc_zero(mem_ctx, to_encrypt->length); + if (blob->data == NULL) { + talloc_free(blob); + return NULL; + } + + rc = gnutls_cipher_init(&cipher_handle, + cipher_algo, + &gkey, + &giv); + if (rc != GNUTLS_E_SUCCESS) { + torture_comment(tctx, + "gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + talloc_free(blob); + return NULL; + } + + rc = gnutls_cipher_encrypt2(cipher_handle, + to_encrypt->data, + to_encrypt->length, + blob->data, + blob->length); + gnutls_cipher_deinit(cipher_handle); + if (rc != GNUTLS_E_SUCCESS) { + torture_comment(tctx, + "gnutls_cipher_decrypt2 failed: %s\n", + gnutls_strerror(rc)); + return NULL; + } + + return blob; +} + +/* + * Certs used for this protocol have a GUID in the issuer_uniq_id field. + * This function fetch it. + */ +static struct GUID *get_cert_guid(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + uint8_t *cert_data, + uint32_t cert_len) +{ + gnutls_x509_crt_t x509_cert = NULL; + gnutls_datum_t x509_crt_data = { + .data = cert_data, + .size = cert_len, + }; + uint8_t dummy[1] = {0}; + DATA_BLOB issuer_unique_id = { + .data = dummy, + .length = 0, + }; + struct GUID *guid = talloc_zero(mem_ctx, struct GUID); + NTSTATUS status; + int rc; + + rc = gnutls_x509_crt_init(&x509_cert); + if (rc != GNUTLS_E_SUCCESS) { + torture_comment(tctx, + "gnutls_x509_crt_init failed - %s", + gnutls_strerror(rc)); + return NULL; + } + + rc = gnutls_x509_crt_import(x509_cert, + &x509_crt_data, + GNUTLS_X509_FMT_DER); + if (rc != GNUTLS_E_SUCCESS) { + torture_comment(tctx, + "gnutls_x509_crt_import failed - %s", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(x509_cert); + return NULL; + } + + /* Get the buffer size */ + rc = gnutls_x509_crt_get_issuer_unique_id(x509_cert, + (char *)issuer_unique_id.data, + &issuer_unique_id.length); + if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER || + issuer_unique_id.length == 0) { + gnutls_x509_crt_deinit(x509_cert); + return NULL; + } + + issuer_unique_id = data_blob_talloc_zero(mem_ctx, + issuer_unique_id.length); + if (issuer_unique_id.data == NULL) { + gnutls_x509_crt_deinit(x509_cert); + return NULL; + } + + rc = gnutls_x509_crt_get_issuer_unique_id(x509_cert, + (char *)issuer_unique_id.data, + &issuer_unique_id.length); + gnutls_x509_crt_deinit(x509_cert); + if (rc != GNUTLS_E_SUCCESS) { + torture_comment(tctx, + "gnutls_x509_crt_get_issuer_unique_id failed - %s", + gnutls_strerror(rc)); + return NULL; + } + + status = GUID_from_data_blob(&issuer_unique_id, guid); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + return guid; +} + +/* + * Encrypt a blob with the private key of the certificate + * passed as a parameter. + */ +static DATA_BLOB *encrypt_blob_pk(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + uint8_t *cert_data, + uint32_t cert_len, + DATA_BLOB *to_encrypt) +{ + gnutls_x509_crt_t x509_cert; + gnutls_datum_t x509_crt_data = { + .data = cert_data, + .size = cert_len, + }; + gnutls_pubkey_t pubkey; + gnutls_datum_t plaintext = { + .data = to_encrypt->data, + .size = to_encrypt->length, + }; + gnutls_datum_t ciphertext = { + .data = NULL, + }; + DATA_BLOB *blob; + int rc; + + rc = gnutls_x509_crt_init(&x509_cert); + if (rc != GNUTLS_E_SUCCESS) { + return NULL; + } + + rc = gnutls_x509_crt_import(x509_cert, + &x509_crt_data, + GNUTLS_X509_FMT_DER); + if (rc != GNUTLS_E_SUCCESS) { + gnutls_x509_crt_deinit(x509_cert); + return NULL; + } + + rc = gnutls_pubkey_init(&pubkey); + if (rc != GNUTLS_E_SUCCESS) { + gnutls_x509_crt_deinit(x509_cert); + return NULL; + } + + rc = gnutls_pubkey_import_x509(pubkey, + x509_cert, + 0); + gnutls_x509_crt_deinit(x509_cert); + if (rc != GNUTLS_E_SUCCESS) { + gnutls_pubkey_deinit(pubkey); + return NULL; + } + + rc = gnutls_pubkey_encrypt_data(pubkey, + 0, + &plaintext, + &ciphertext); + gnutls_pubkey_deinit(pubkey); + if (rc != GNUTLS_E_SUCCESS) { + return NULL; + } + + blob = talloc_zero(mem_ctx, DATA_BLOB); + if (blob == NULL) { + gnutls_pubkey_deinit(pubkey); + return NULL; + } + + *blob = data_blob_talloc(blob, ciphertext.data, ciphertext.size); + gnutls_free(ciphertext.data); + if (blob->data == NULL) { + gnutls_pubkey_deinit(pubkey); + return NULL; + } + + return blob; +} + +static struct bkrp_BackupKey *createRetrieveBackupKeyGUIDStruct(struct torture_context *tctx, + struct dcerpc_pipe *p, int version, DATA_BLOB *out) +{ + struct dcerpc_binding *binding; + struct bkrp_client_side_wrapped data; + struct GUID *g = talloc(tctx, struct GUID); + struct bkrp_BackupKey *r = talloc_zero(tctx, struct bkrp_BackupKey); + enum ndr_err_code ndr_err; + DATA_BLOB blob; + NTSTATUS status; + + if (r == NULL) { + return NULL; + } + + binding = dcerpc_binding_dup(tctx, p->binding); + if (binding == NULL) { + return NULL; + } + + status = dcerpc_binding_set_flags(binding, DCERPC_SEAL|DCERPC_AUTH_SPNEGO, 0); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + ZERO_STRUCT(data); + status = GUID_from_string(BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID, g); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + r->in.guidActionAgent = g; + data.version = version; + ndr_err = ndr_push_struct_blob(&blob, tctx, &data, + (ndr_push_flags_fn_t)ndr_push_bkrp_client_side_wrapped); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + r->in.data_in = blob.data; + r->in.data_in_len = blob.length; + r->out.data_out = &out->data; + r->out.data_out_len = talloc(r, uint32_t); + return r; +} + +static struct bkrp_BackupKey *createRestoreGUIDStruct(struct torture_context *tctx, + struct dcerpc_pipe *p, int version, DATA_BLOB *out, + bool norevert, + bool broken_version, + bool broken_user, + bool broken_magic_secret, + bool broken_magic_access, + bool broken_hash_access, + bool broken_cert_guid) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct bkrp_client_side_wrapped data; + DATA_BLOB *xs; + DATA_BLOB *sec; + DATA_BLOB *enc_sec = NULL; + DATA_BLOB *enc_xs = NULL; + DATA_BLOB *blob2; + DATA_BLOB enc_sec_reverted; + DATA_BLOB key; + DATA_BLOB iv; + DATA_BLOB out_blob; + struct GUID *guid, *g; + int t; + uint32_t size; + enum ndr_err_code ndr_err; + NTSTATUS status; + const char *user; + gnutls_cipher_algorithm_t cipher_algo; + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, version, &out_blob); + if (r == NULL) { + return NULL; + } + + if (broken_user) { + /* we take a fake user*/ + user = "guest"; + } else { + user = cli_credentials_get_username( + samba_cmdline_get_creds()); + } + + + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + "Get GUID"); + torture_assert_werr_ok(tctx, r->out.result, + "Get GUID"); + + /* + * We have to set it outside of the function createRetrieveBackupKeyGUIDStruct + * the len of the blob, this is due to the fact that they don't have the + * same size (one is 32bits the other 64bits) + */ + out_blob.length = *r->out.data_out_len; + + sec = create_unencryptedsecret(tctx, broken_magic_secret, version); + if (sec == NULL) { + return NULL; + } + + xs = create_access_check(tctx, p, tctx, user, broken_hash_access, version); + if (xs == NULL) { + return NULL; + } + + if (broken_magic_access){ + /* The start of the access_check structure contains the + * GUID of the certificate + */ + xs->data[0]++; + } + + enc_sec = encrypt_blob_pk(tctx, tctx, out_blob.data, out_blob.length, sec); + if (!enc_sec) { + return NULL; + } + enc_sec_reverted.data = talloc_array(tctx, uint8_t, enc_sec->length); + if (enc_sec_reverted.data == NULL) { + return NULL; + } + enc_sec_reverted.length = enc_sec->length; + + /* + * We DO NOT revert the array on purpose it's in order to check that + * when the server is not able to decrypt then it answer the correct error + */ + if (norevert) { + for(t=0; t< enc_sec->length; t++) { + enc_sec_reverted.data[t] = ((uint8_t*)enc_sec->data)[t]; + } + } else { + for(t=0; t< enc_sec->length; t++) { + enc_sec_reverted.data[t] = ((uint8_t*)enc_sec->data)[enc_sec->length - t -1]; + } + } + + size = sec->length; + switch (version) { + case 2: + cipher_algo = GNUTLS_CIPHER_3DES_CBC; + break; + case 3: + cipher_algo = GNUTLS_CIPHER_AES_256_CBC; + break; + default: + return NULL; + } + iv.length = gnutls_cipher_get_iv_size(cipher_algo); + iv.data = sec->data + (size - iv.length); + + key.length = gnutls_cipher_get_key_size(cipher_algo); + key.data = sec->data + (size - (key.length + iv.length)); + + enc_xs = encrypt_blob(tctx, tctx, &key, &iv, xs, cipher_algo); + if (!enc_xs) { + return NULL; + } + + /* To cope with the fact that heimdal do padding at the end for the moment */ + enc_xs->length = xs->length; + + guid = get_cert_guid(tctx, tctx, out_blob.data, out_blob.length); + if (guid == NULL) { + return NULL; + } + + if (broken_version) { + data.version = 1; + } else { + data.version = version; + } + + data.guid = *guid; + data.encrypted_secret = enc_sec_reverted.data; + data.access_check = enc_xs->data; + data.encrypted_secret_len = enc_sec->length; + data.access_check_len = enc_xs->length; + + /* We want the blob to persist after this function so we don't + * allocate it in the stack + */ + blob2 = talloc(tctx, DATA_BLOB); + if (blob2 == NULL) { + return NULL; + } + + ndr_err = ndr_push_struct_blob(blob2, tctx, &data, + (ndr_push_flags_fn_t)ndr_push_bkrp_client_side_wrapped); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + + if (broken_cert_guid) { + blob2->data[12]++; + } + + ZERO_STRUCT(*r); + + g = talloc(tctx, struct GUID); + if (g == NULL) { + return NULL; + } + + status = GUID_from_string(BACKUPKEY_RESTORE_GUID, g); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + r->in.guidActionAgent = g; + r->in.data_in = blob2->data; + r->in.data_in_len = blob2->length; + r->in.param = 0; + r->out.data_out = &(out->data); + r->out.data_out_len = talloc(r, uint32_t); + return r; +} + +/* Check that we are able to receive the certificate of the DCs + * used for client wrap version of the backup key protocol + */ +static bool test_RetrieveBackupKeyGUID(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + if (r == NULL) { + return false; + } + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, r), + "Get GUID"); + + out_blob.length = *r->out.data_out_len; + torture_assert_werr_equal(tctx, + r->out.result, + WERR_OK, + "Wrong dce/rpc error code"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, + "Get GUID"); + } + return true; +} + +/* Test to check the failure to recover a secret because the + * secret blob is not reversed + */ +static bool test_RestoreGUID_ko(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + true, false, false, false, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_PARAMETER, "Wrong error code"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_wrongversion(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + false, true, false, false, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_PARAMETER, "Wrong error code on wrong version"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_wronguser(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + false, false, true, false, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_ACCESS, "Restore GUID"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_v3(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 3, &out_blob, + false, false, false, false, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 1, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_OK, "Restore GUID"); + torture_assert_str_equal(tctx, (char*)resp.secret.data, secret, "Wrong secret"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + false, false, false, false, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + torture_assert_werr_equal(tctx, r->out.result, WERR_OK, "Restore GUID"); + torture_assert_ndr_err_equal(tctx, + ndr_pull_struct_blob(&out_blob, tctx, &resp, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped), + NDR_ERR_SUCCESS, + "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_str_equal(tctx, (char*)resp.secret.data, secret, "Wrong secret"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_badmagiconsecret(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 3, &out_blob, + false, false, false, true, false, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_DATA, "Wrong error code while providing bad magic in secret"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_emptyrequest(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 3, &out_blob, + false, false, false, true, false, false, true); + + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + r->in.data_in = talloc(tctx, uint8_t); + r->in.data_in_len = 0; + r->in.param = 0; + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_PARAMETER, "Bad error code on wrong has in access check"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_badcertguid(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 3, &out_blob, + false, false, false, false, false, false, true); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct() failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + + /* + * Windows 2012R2 has, presumably, a programming error + * returning an NTSTATUS code on this interface + */ + if (W_ERROR_V(r->out.result) != NT_STATUS_V(NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_DATA, "Bad error code on wrong has in access check"); + } + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_badmagicaccesscheck(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + false, false, false, false, true, false, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_DATA, "Bad error code on wrong has in access check"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +static bool test_RestoreGUID_badhashaccesscheck(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum ndr_err_code ndr_err; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_client_side_unwrapped resp; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + struct bkrp_BackupKey *r = createRestoreGUIDStruct(tctx, p, 2, &out_blob, + false, false, false, false, false, true, false); + torture_assert(tctx, r != NULL, "createRestoreGUIDStruct failed"); + torture_assert_ntstatus_ok(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), "Restore GUID"); + out_blob.length = *r->out.data_out_len; + ndr_err = ndr_pull_struct_blob(&out_blob, tctx, &resp, (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_unwrapped); + torture_assert_int_equal(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), 0, "Unable to unmarshall bkrp_client_side_unwrapped"); + torture_assert_werr_equal(tctx, r->out.result, WERR_INVALID_DATA, "Bad error code on wrong has in access check"); + } else { + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + torture_assert_ntstatus_equal(tctx, dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, "Get GUID"); + } + + return true; +} + +/* + * Check that the RSA modulus in the certificate of the DCs has 2048 bits. + */ +static bool test_RetrieveBackupKeyGUID_validate(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB out_blob; + struct bkrp_BackupKey *r = createRetrieveBackupKeyGUIDStruct(tctx, p, 2, &out_blob); + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + torture_assert(tctx, r != NULL, "test_RetrieveBackupKeyGUID_validate failed"); + + if (r == NULL) { + return false; + } + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + gnutls_x509_crt_t x509_cert = NULL; + gnutls_pubkey_t pubkey = NULL; + gnutls_datum_t x509_crt_data; + gnutls_pk_algorithm_t pubkey_algo; + uint8_t dummy[1] = {0}; + DATA_BLOB subject_unique_id = { + .data = dummy, + .length = 0, + }; + DATA_BLOB issuer_unique_id = { + .data = dummy, + .length = 0, + }; + DATA_BLOB reversed = { + .data = dummy, + .length = 0, + }; + DATA_BLOB serial_number; + unsigned int RSA_returned_bits = 0; + int version; + size_t i; + int cmp; + int rc; + + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, r), + "Get GUID"); + + torture_assert_werr_ok(tctx, r->out.result, + "Get GUID"); + + out_blob.length = *r->out.data_out_len; + + x509_crt_data.data = out_blob.data; + x509_crt_data.size = out_blob.length; + + rc = gnutls_x509_crt_init(&x509_cert); + if (rc != GNUTLS_E_SUCCESS) { + return NULL; + } + + rc = gnutls_x509_crt_import(x509_cert, + &x509_crt_data, + GNUTLS_X509_FMT_DER); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_x509_crt_import failed"); + + /* Compare unique ids */ + + /* Get buffer size */ + rc = gnutls_x509_crt_get_subject_unique_id(x509_cert, + (char *)subject_unique_id.data, + &subject_unique_id.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SHORT_MEMORY_BUFFER, + "gnutls_x509_crt_get_subject_unique_id " + "get buffer size failed"); + + subject_unique_id = data_blob_talloc_zero(tctx, + subject_unique_id.length); + + rc = gnutls_x509_crt_get_subject_unique_id(x509_cert, + (char *)subject_unique_id.data, + &subject_unique_id.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_x509_crt_get_subject_unique_id failed"); + + rc = gnutls_x509_crt_get_issuer_unique_id(x509_cert, + (char *)issuer_unique_id.data, + &issuer_unique_id.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SHORT_MEMORY_BUFFER, + "gnutls_x509_crt_get_issuer_unique_id " + "get buffer size failed"); + + issuer_unique_id = data_blob_talloc_zero(tctx, + issuer_unique_id.length); + + rc = gnutls_x509_crt_get_issuer_unique_id(x509_cert, + (char *)issuer_unique_id.data, + &issuer_unique_id.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_x509_crt_get_issuer_unique_id failed"); + + cmp = data_blob_cmp(&subject_unique_id, &issuer_unique_id); + torture_assert(tctx, + cmp == 0, + "The GUID to identify the public key is not " + "identical"); + + rc = gnutls_x509_crt_get_serial(x509_cert, + reversed.data, + &reversed.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SHORT_MEMORY_BUFFER, + "gnutls_x509_crt_get_serial " + "get buffer size failed"); + + reversed = data_blob_talloc_zero(tctx, + reversed.length); + + rc = gnutls_x509_crt_get_serial(x509_cert, + reversed.data, + &reversed.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_x509_crt_get_serial failed"); + + /* + * Heimdal sometimes adds a leading byte to the data buffer of + * the serial number. So lets uses the subject_unique_id size + * and ignore the leading byte. + */ + serial_number = data_blob_talloc_zero(tctx, + subject_unique_id.length); + + for (i = 0; i < serial_number.length; i++) { + serial_number.data[i] = reversed.data[reversed.length - i - 1]; + } + + cmp = data_blob_cmp(&subject_unique_id, &serial_number); + torture_assert(tctx, + cmp == 0, + "The GUID to identify the public key is not " + "identical"); + + /* Check certificate version */ + version = gnutls_x509_crt_get_version(x509_cert); + torture_assert_int_equal(tctx, + version, + 3, + "Invalid certificate version"); + + /* Get the public key */ + rc = gnutls_pubkey_init(&pubkey); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_pubkey_init failed"); + + rc = gnutls_pubkey_import_x509(pubkey, + x509_cert, + 0); + gnutls_x509_crt_deinit(x509_cert); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_pubkey_import_x509 failed"); + + pubkey_algo = gnutls_pubkey_get_pk_algorithm(pubkey, + &RSA_returned_bits); + gnutls_pubkey_deinit(pubkey); + torture_assert_int_equal(tctx, + pubkey_algo, + GNUTLS_PK_RSA, + "gnutls_pubkey_get_pk_algorithm did " + "not return a RSA key"); + torture_assert_int_equal(tctx, + RSA_returned_bits, + 2048, + "RSA Key doesn't have 2048 bits"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, r), + NT_STATUS_ACCESS_DENIED, + "Get GUID"); + } + + return true; +} + +static bool test_ServerWrap_encrypt_decrypt(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct bkrp_BackupKey r; + struct GUID guid; + DATA_BLOB plaintext = data_blob_const(secret, sizeof(secret)); + DATA_BLOB encrypted; + uint32_t enclen; + DATA_BLOB decrypted; + uint32_t declen; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + ZERO_STRUCT(r); + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + /* Encrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_BACKUP_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = plaintext.data; + r.in.data_in_len = plaintext.length; + r.in.param = 0; + r.out.data_out = &encrypted.data; + r.out.data_out_len = &enclen; + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "encrypt"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_ACCESS_DENIED, + "encrypt"); + return true; + } + torture_assert_werr_ok(tctx, + r.out.result, + "encrypt"); + encrypted.length = *r.out.data_out_len; + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_ok(tctx, + r.out.result, + "decrypt"); + decrypted.length = *r.out.data_out_len; + + /* Compare */ + torture_assert_data_blob_equal(tctx, plaintext, decrypted, "Decrypt failed"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_ok(tctx, + r.out.result, + "decrypt"); + decrypted.length = *r.out.data_out_len; + + /* Compare */ + torture_assert_data_blob_equal(tctx, plaintext, decrypted, "Decrypt failed"); + return true; +} + +static bool test_ServerWrap_decrypt_wrong_keyGUID(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct bkrp_BackupKey r; + struct GUID guid; + DATA_BLOB plaintext = data_blob_const(secret, sizeof(secret)); + DATA_BLOB encrypted; + uint32_t enclen; + DATA_BLOB decrypted; + uint32_t declen; + struct dcerpc_binding_handle *b = p->binding_handle; + enum ndr_err_code ndr_err; + struct bkrp_server_side_wrapped server_side_wrapped; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + ZERO_STRUCT(r); + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + /* Encrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_BACKUP_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = plaintext.data; + r.in.data_in_len = plaintext.length; + r.in.param = 0; + r.out.data_out = &encrypted.data; + r.out.data_out_len = &enclen; + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "encrypt"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_ACCESS_DENIED, + "encrypt"); + return true; + } + torture_assert_werr_ok(tctx, + r.out.result, + "encrypt"); + encrypted.length = *r.out.data_out_len; + + ndr_err = ndr_pull_struct_blob(&encrypted, tctx, &server_side_wrapped, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_server_side_wrapped); + torture_assert_ndr_err_equal(tctx, ndr_err, NDR_ERR_SUCCESS, "pull of server_side_wrapped"); + + /* Change the GUID */ + server_side_wrapped.guid = GUID_random(); + + ndr_err = ndr_push_struct_blob(&encrypted, tctx, &server_side_wrapped, + (ndr_push_flags_fn_t)ndr_push_bkrp_server_side_wrapped); + torture_assert_ndr_err_equal(tctx, ndr_err, NDR_ERR_SUCCESS, "push of server_side_wrapped"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_DATA, + "decrypt should fail with WERR_INVALID_DATA"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_DATA, + "decrypt should fail with WERR_INVALID_DATA"); + + return true; +} + +static bool test_ServerWrap_decrypt_empty_request(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct bkrp_BackupKey r; + struct GUID guid; + DATA_BLOB decrypted; + uint32_t declen; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t short_request[4] = { 1, 0, 0, 0 }; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + ZERO_STRUCT(r); + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 0; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "encrypt"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_ACCESS_DENIED, + "encrypt"); + return true; + } + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 0; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = NULL; + r.in.data_in_len = 0; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_INVALID_PARAMETER_MIX, + "decrypt"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = NULL; + r.in.data_in_len = 0; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_INVALID_PARAMETER_MIX, + "decrypt"); + + return true; +} + + +static bool test_ServerWrap_decrypt_short_request(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct bkrp_BackupKey r; + struct GUID guid; + DATA_BLOB decrypted; + uint32_t declen; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t short_request[4] = { 1, 0, 0, 0 }; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + ZERO_STRUCT(r); + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 4; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "encrypt"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_ACCESS_DENIED, + "encrypt"); + return true; + } + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARM"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 4; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 1; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = short_request; + r.in.data_in_len = 1; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + + return true; +} + +static bool test_ServerWrap_encrypt_decrypt_manual(struct torture_context *tctx, + struct bkrp_server_side_wrapped *server_side_wrapped, + enum test_wrong wrong) +{ + char *lsa_binding_string = NULL; + struct dcerpc_binding *lsa_binding = NULL; + struct dcerpc_pipe *lsa_p = NULL; + struct dcerpc_binding_handle *lsa_b = NULL; + struct lsa_OpenSecret r_secret; + struct lsa_QuerySecret r_query_secret; + struct policy_handle *handle, sec_handle; + struct bkrp_BackupKey r; + struct GUID preferred_key_guid; + DATA_BLOB plaintext = data_blob_const(secret, sizeof(secret)); + DATA_BLOB preferred_key, preferred_key_clear, session_key, + decrypt_key, decrypt_key_clear, encrypted_blob, + sid_blob; + struct bkrp_dc_serverwrap_key server_key; + struct lsa_DATA_BUF_PTR bufp1; + char *key_guid_string; + 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; + + ZERO_STRUCT(r); + ZERO_STRUCT(r_secret); + ZERO_STRUCT(r_query_secret); + + /* Now read BCKUPKEY_P and prove we can do a matching decrypt and encrypt */ + + /* lsa_OpenSecret only works with ncacn_np and AUTH_LEVEL_NONE */ + lsa_binding_string = talloc_asprintf(tctx, "ncacn_np:%s", + torture_setting_string(tctx, "host", NULL)); + torture_assert(tctx, lsa_binding_string != NULL, "lsa_binding_string"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_parse_binding(tctx, lsa_binding_string, &lsa_binding), + "Failed to parse dcerpc binding"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &lsa_p, + lsa_binding, &ndr_table_lsarpc, + samba_cmdline_get_creds(), + tctx->ev, tctx->lp_ctx), + "Opening LSA pipe"); + lsa_b = lsa_p->binding_handle; + + torture_assert(tctx, test_lsa_OpenPolicy2(lsa_b, tctx, &handle), "OpenPolicy failed"); + r_secret.in.name.string = "G$BCKUPKEY_P"; + + r_secret.in.handle = handle; + r_secret.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r_secret.out.sec_handle = &sec_handle; + + torture_comment(tctx, "Testing OpenSecret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenSecret_r(lsa_b, tctx, &r_secret), + "OpenSecret failed"); + torture_assert_ntstatus_ok(tctx, r_secret.out.result, + "OpenSecret failed"); + + r_query_secret.in.sec_handle = &sec_handle; + r_query_secret.in.new_val = &bufp1; + bufp1.buf = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(lsa_b, tctx, &r_query_secret), + "QuerySecret failed"); + torture_assert_ntstatus_ok(tctx, r_query_secret.out.result, + "QuerySecret failed"); + + + preferred_key.data = r_query_secret.out.new_val->buf->data; + preferred_key.length = r_query_secret.out.new_val->buf->size; + torture_assert_ntstatus_ok(tctx, dcerpc_fetch_session_key(lsa_p, &session_key), + "dcerpc_fetch_session_key failed"); + + torture_assert_ntstatus_ok(tctx, + sess_decrypt_blob(tctx, + &preferred_key, &session_key, &preferred_key_clear), + "sess_decrypt_blob failed"); + + torture_assert_ntstatus_ok(tctx, GUID_from_ndr_blob(&preferred_key_clear, &preferred_key_guid), + "GUID parse failed"); + + torture_assert_guid_equal(tctx, server_side_wrapped->guid, + preferred_key_guid, + "GUID didn't match value pointed at by G$BCKUPKEY_P"); + + /* And read BCKUPKEY_<guid> and get the actual key */ + + key_guid_string = GUID_string(tctx, &server_side_wrapped->guid); + r_secret.in.name.string = talloc_asprintf(tctx, "G$BCKUPKEY_%s", key_guid_string); + + r_secret.in.handle = handle; + r_secret.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r_secret.out.sec_handle = &sec_handle; + + torture_comment(tctx, "Testing OpenSecret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenSecret_r(lsa_b, tctx, &r_secret), + "OpenSecret failed"); + torture_assert_ntstatus_ok(tctx, r_secret.out.result, + "OpenSecret failed"); + + r_query_secret.in.sec_handle = &sec_handle; + r_query_secret.in.new_val = &bufp1; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(lsa_b, tctx, &r_query_secret), + "QuerySecret failed"); + torture_assert_ntstatus_ok(tctx, r_query_secret.out.result, + "QuerySecret failed"); + + + decrypt_key.data = r_query_secret.out.new_val->buf->data; + decrypt_key.length = r_query_secret.out.new_val->buf->size; + + torture_assert_ntstatus_ok(tctx, + sess_decrypt_blob(tctx, + &decrypt_key, &session_key, &decrypt_key_clear), + "sess_decrypt_blob failed"); + + torture_assert_ndr_err_equal(tctx, ndr_pull_struct_blob(&decrypt_key_clear, tctx, &server_key, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_dc_serverwrap_key), + NDR_ERR_SUCCESS, "Failed to parse server_key"); + + torture_assert_int_equal(tctx, server_key.magic, 1, "Failed to correctly decrypt server key"); + + /* + * 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 + */ + gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + server_key.key, + sizeof(server_key.key)); + gnutls_hmac(hmac_hnd, + server_side_wrapped->r2, + sizeof(server_side_wrapped->r2)); + gnutls_hmac_output(hmac_hnd, symkey); + + /* rc4 decrypt sid and secret using sym key */ + cipher_key.data = symkey; + cipher_key.size = sizeof(symkey); + + encrypted_blob = data_blob_talloc(tctx, server_side_wrapped->rc4encryptedpayload, + server_side_wrapped->ciphertext_length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &cipher_key, + NULL); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_cipher_init failed"); + rc = gnutls_cipher_encrypt2(cipher_hnd, + encrypted_blob.data, + encrypted_blob.length, + encrypted_blob.data, + encrypted_blob.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_cipher_encrypt failed"); + gnutls_cipher_deinit(cipher_hnd); + + torture_assert_ndr_err_equal(tctx, ndr_pull_struct_blob(&encrypted_blob, tctx, &rc4payload, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_rc4encryptedpayload), + NDR_ERR_SUCCESS, "Failed to parse rc4encryptedpayload"); + + torture_assert_int_equal(tctx, rc4payload.secret_data.length, + server_side_wrapped->payload_length, + "length of decrypted payload not the length declared in surrounding structure"); + + /* + * 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 + */ + gnutls_hmac(hmac_hnd, + rc4payload.r3, + sizeof(rc4payload.r3)); + gnutls_hmac_deinit(hmac_hnd, mackey); + + torture_assert_ndr_err_equal(tctx, ndr_push_struct_blob(&sid_blob, tctx, &rc4payload.sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid), + NDR_ERR_SUCCESS, "unable to push SID"); + + gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + mackey, + sizeof(mackey)); + /* SID field */ + gnutls_hmac(hmac_hnd, + sid_blob.data, + sid_blob.length); + /* Secret field */ + gnutls_hmac(hmac_hnd, + rc4payload.secret_data.data, + rc4payload.secret_data.length); + gnutls_hmac_output(hmac_hnd, mac); + + torture_assert_mem_equal(tctx, mac, rc4payload.mac, sizeof(mac), "mac not correct"); + torture_assert_int_equal(tctx, rc4payload.secret_data.length, + plaintext.length, "decrypted data is not correct length"); + torture_assert_mem_equal(tctx, rc4payload.secret_data.data, + plaintext.data, plaintext.length, + "decrypted data is not correct"); + + /* Not strictly correct all the time, but good enough for this test */ + caller_sid = get_user_sid(tctx, tctx, + cli_credentials_get_username( + samba_cmdline_get_creds())); + + torture_assert_sid_equal(tctx, &rc4payload.sid, caller_sid, "Secret saved with wrong SID"); + + + /* RE-encrypt */ + + if (wrong == WRONG_SID) { + rc4payload.sid.sub_auths[rc4payload.sid.num_auths - 1] = DOMAIN_RID_KRBTGT; + } + + dump_data_pw("mackey: \n", mackey, sizeof(mackey)); + + torture_assert_ndr_err_equal(tctx, + ndr_push_struct_blob(&sid_blob, tctx, &rc4payload.sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid), + NDR_ERR_SUCCESS, + "push of sid failed"); + + /* SID field */ + gnutls_hmac(hmac_hnd, + sid_blob.data, + sid_blob.length); + /* Secret field */ + gnutls_hmac(hmac_hnd, + rc4payload.secret_data.data, + rc4payload.secret_data.length); + gnutls_hmac_deinit(hmac_hnd, rc4payload.mac); + + dump_data_pw("rc4payload.mac: \n", rc4payload.mac, sizeof(rc4payload.mac)); + + torture_assert_ndr_err_equal(tctx, + ndr_push_struct_blob(&encrypted_blob, tctx, &rc4payload, + (ndr_push_flags_fn_t)ndr_push_bkrp_rc4encryptedpayload), + NDR_ERR_SUCCESS, + "push of rc4payload failed"); + + if (wrong == WRONG_KEY) { + symkey[0] = 78; + symkey[1] = 78; + symkey[2] = 78; + } + + /* 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); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_cipher_init failed"); + rc = gnutls_cipher_encrypt2(cipher_hnd, + encrypted_blob.data, + encrypted_blob.length, + encrypted_blob.data, + encrypted_blob.length); + torture_assert_int_equal(tctx, + rc, + GNUTLS_E_SUCCESS, + "gnutls_cipher_encrypt failed"); + gnutls_cipher_deinit(cipher_hnd); + + + /* re-create server wrap structure */ + + torture_assert_int_equal(tctx, encrypted_blob.length, + server_side_wrapped->ciphertext_length, + "expected encrypted length not to change"); + if (wrong == RIGHT_KEY) { + torture_assert_mem_equal(tctx, server_side_wrapped->rc4encryptedpayload, + encrypted_blob.data, + encrypted_blob.length, + "expected encrypted data not to change"); + } + + server_side_wrapped->payload_length = rc4payload.secret_data.length; + server_side_wrapped->ciphertext_length = encrypted_blob.length; + server_side_wrapped->rc4encryptedpayload = encrypted_blob.data; + + return true; +} + + +static bool test_ServerWrap_decrypt_wrong_stuff(struct torture_context *tctx, + struct dcerpc_pipe *p, + enum test_wrong wrong) +{ + struct bkrp_BackupKey r; + struct GUID guid; + DATA_BLOB plaintext = data_blob_const(secret, sizeof(secret)); + DATA_BLOB encrypted; + uint32_t enclen; + DATA_BLOB decrypted; + uint32_t declen; + struct dcerpc_binding_handle *b = p->binding_handle; + enum ndr_err_code ndr_err; + struct bkrp_server_side_wrapped server_side_wrapped; + bool repush = false; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + ZERO_STRUCT(r); + + dcerpc_binding_handle_auth_info(b, &auth_type, &auth_level); + + /* Encrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_BACKUP_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = plaintext.data; + r.in.data_in_len = plaintext.length; + r.in.param = 0; + r.out.data_out = &encrypted.data; + r.out.data_out_len = &enclen; + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "encrypt"); + } else { + torture_assert_ntstatus_equal(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + NT_STATUS_ACCESS_DENIED, + "encrypt"); + return true; + } + torture_assert_werr_ok(tctx, + r.out.result, + "encrypt"); + encrypted.length = *r.out.data_out_len; + + ndr_err = ndr_pull_struct_blob(&encrypted, tctx, &server_side_wrapped, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_server_side_wrapped); + torture_assert_ndr_err_equal(tctx, ndr_err, NDR_ERR_SUCCESS, "pull of server_side_wrapped"); + + torture_assert_int_equal(tctx, server_side_wrapped.payload_length, plaintext.length, + "wrong payload length"); + + switch (wrong) { + case WRONG_MAGIC: + /* Change the magic. Forced by our NDR layer, so do it raw */ + SIVAL(encrypted.data, 0, 78); /* valid values are 1-3 */ + break; + case WRONG_R2: + server_side_wrapped.r2[0] = 78; + server_side_wrapped.r2[1] = 78; + server_side_wrapped.r2[3] = 78; + repush = true; + break; + case WRONG_PAYLOAD_LENGTH: + server_side_wrapped.payload_length = UINT32_MAX - 8; + repush = true; + break; + case WRONG_CIPHERTEXT_LENGTH: + /* + * Change the ciphertext len. We can't push this if + * we have it wrong, so do it raw + */ + SIVAL(encrypted.data, 8, UINT32_MAX - 8); /* valid values are 1-3 */ + break; + case SHORT_PAYLOAD_LENGTH: + server_side_wrapped.payload_length = server_side_wrapped.payload_length - 8; + repush = true; + break; + case SHORT_CIPHERTEXT_LENGTH: + /* + * Change the ciphertext len. We can't push this if + * we have it wrong, so do it raw + */ + SIVAL(encrypted.data, 8, server_side_wrapped.ciphertext_length - 8); /* valid values are 1-3 */ + break; + case ZERO_PAYLOAD_LENGTH: + server_side_wrapped.payload_length = 0; + repush = true; + break; + case ZERO_CIPHERTEXT_LENGTH: + /* + * Change the ciphertext len. We can't push this if + * we have it wrong, so do it raw + */ + SIVAL(encrypted.data, 8, 0); /* valid values are 1-3 */ + break; + + case RIGHT_KEY: + case WRONG_KEY: + case WRONG_SID: + torture_assert(tctx, + test_ServerWrap_encrypt_decrypt_manual(tctx, &server_side_wrapped, wrong), + "test_ServerWrap_encrypt_decrypt_manual failed"); + repush = true; + break; + } + + if (repush) { + ndr_err = ndr_push_struct_blob(&encrypted, tctx, &server_side_wrapped, + (ndr_push_flags_fn_t)ndr_push_bkrp_server_side_wrapped); + torture_assert_ndr_err_equal(tctx, ndr_err, NDR_ERR_SUCCESS, "push of server_side_wrapped"); + } + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + + if ((wrong == WRONG_R2 || wrong == WRONG_KEY) + && W_ERROR_EQUAL(r.out.result, WERR_INVALID_SID)) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_SID, + "decrypt should fail with WERR_INVALID_SID or WERR_INVALID_PARAMETER"); + } else if (wrong == RIGHT_KEY) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_OK, + "decrypt should succeed!"); + } else if (wrong == WRONG_SID) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_ACCESS, + "decrypt should fail with WERR_INVALID_ACCESS"); + } else { + if (!W_ERROR_EQUAL(r.out.result, WERR_INVALID_ACCESS) + && !W_ERROR_EQUAL(r.out.result, WERR_INVALID_PARAMETER)) { + torture_assert_werr_equal(tctx, r.out.result, + WERR_INVALID_DATA, + "decrypt should fail with WERR_INVALID_ACCESS, WERR_INVALID_PARAMETER or WERR_INVALID_DATA"); + } + } + + /* Decrypt */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(BACKUPKEY_RESTORE_GUID_WIN2K, &guid), + "obtain GUID"); + + r.in.guidActionAgent = &guid; + r.in.data_in = encrypted.data; + r.in.data_in_len = encrypted.length; + r.in.param = 0; + r.out.data_out = &(decrypted.data); + r.out.data_out_len = &declen; + torture_assert_ntstatus_ok(tctx, + dcerpc_bkrp_BackupKey_r(b, tctx, &r), + "decrypt"); + + if ((wrong == WRONG_R2 || wrong == WRONG_KEY) + && W_ERROR_EQUAL(r.out.result, WERR_INVALID_SID)) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_SID, + "decrypt should fail with WERR_INVALID_SID or WERR_INVALID_PARAMETER"); + } else if (wrong == RIGHT_KEY) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_OK, + "decrypt should succeed!"); + } else if (wrong == WRONG_SID) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_ACCESS, + "decrypt should fail with WERR_INVALID_ACCESS"); + } else { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "decrypt should fail with WERR_INVALID_PARAMETER"); + } + + return true; +} + +static bool test_ServerWrap_decrypt_wrong_magic(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_MAGIC); +} + +static bool test_ServerWrap_decrypt_wrong_r2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_R2); +} + +static bool test_ServerWrap_decrypt_wrong_payload_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_PAYLOAD_LENGTH); +} + +static bool test_ServerWrap_decrypt_short_payload_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, SHORT_PAYLOAD_LENGTH); +} + +static bool test_ServerWrap_decrypt_zero_payload_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, ZERO_PAYLOAD_LENGTH); +} + +static bool test_ServerWrap_decrypt_wrong_ciphertext_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_CIPHERTEXT_LENGTH); +} + +static bool test_ServerWrap_decrypt_short_ciphertext_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, SHORT_CIPHERTEXT_LENGTH); +} + +static bool test_ServerWrap_decrypt_zero_ciphertext_length(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, ZERO_CIPHERTEXT_LENGTH); +} + +static bool test_ServerWrap_encrypt_decrypt_remote_key(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, RIGHT_KEY); +} + +static bool test_ServerWrap_encrypt_decrypt_wrong_key(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_KEY); +} + +static bool test_ServerWrap_encrypt_decrypt_wrong_sid(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_ServerWrap_decrypt_wrong_stuff(tctx, p, WRONG_SID); +} + +struct torture_suite *torture_rpc_backupkey(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "backupkey"); + + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "backupkey", + &ndr_table_backupkey); + + torture_rpc_tcase_add_test(tcase, "retreive_backup_key_guid", + test_RetrieveBackupKeyGUID); + + torture_rpc_tcase_add_test(tcase, "restore_guid", + test_RestoreGUID); + + torture_rpc_tcase_add_test(tcase, "restore_guid version 3", + test_RestoreGUID_v3); + +/* We double the test in order to be sure that we don't mess stuff (ie. freeing static stuff) */ + + torture_rpc_tcase_add_test(tcase, "restore_guid_2nd", + test_RestoreGUID); + + torture_rpc_tcase_add_test(tcase, "unable_to_decrypt_secret", + test_RestoreGUID_ko); + + torture_rpc_tcase_add_test(tcase, "wrong_user_restore_guid", + test_RestoreGUID_wronguser); + + torture_rpc_tcase_add_test(tcase, "wrong_version_restore_guid", + test_RestoreGUID_wrongversion); + + torture_rpc_tcase_add_test(tcase, "bad_magic_on_secret_restore_guid", + test_RestoreGUID_badmagiconsecret); + + torture_rpc_tcase_add_test(tcase, "bad_hash_on_secret_restore_guid", + test_RestoreGUID_badhashaccesscheck); + + torture_rpc_tcase_add_test(tcase, "bad_magic_on_accesscheck_restore_guid", + test_RestoreGUID_badmagicaccesscheck); + + torture_rpc_tcase_add_test(tcase, "bad_cert_guid_restore_guid", + test_RestoreGUID_badcertguid); + + torture_rpc_tcase_add_test(tcase, "empty_request_restore_guid", + test_RestoreGUID_emptyrequest); + + torture_rpc_tcase_add_test(tcase, "retreive_backup_key_guid_validate", + test_RetrieveBackupKeyGUID_validate); + + torture_rpc_tcase_add_test(tcase, "server_wrap_encrypt_decrypt", + test_ServerWrap_encrypt_decrypt); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_wrong_keyGUID", + test_ServerWrap_decrypt_wrong_keyGUID); + + torture_rpc_tcase_add_test(tcase, "server_wrap_empty_request", + test_ServerWrap_decrypt_empty_request); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_short_request", + test_ServerWrap_decrypt_short_request); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_wrong_magic", + test_ServerWrap_decrypt_wrong_magic); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_wrong_r2", + test_ServerWrap_decrypt_wrong_r2); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_wrong_payload_length", + test_ServerWrap_decrypt_wrong_payload_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_short_payload_length", + test_ServerWrap_decrypt_short_payload_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_zero_payload_length", + test_ServerWrap_decrypt_zero_payload_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_wrong_ciphertext_length", + test_ServerWrap_decrypt_wrong_ciphertext_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_short_ciphertext_length", + test_ServerWrap_decrypt_short_ciphertext_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_decrypt_zero_ciphertext_length", + test_ServerWrap_decrypt_zero_ciphertext_length); + + torture_rpc_tcase_add_test(tcase, "server_wrap_encrypt_decrypt_remote_key", + test_ServerWrap_encrypt_decrypt_remote_key); + + torture_rpc_tcase_add_test(tcase, "server_wrap_encrypt_decrypt_wrong_key", + test_ServerWrap_encrypt_decrypt_wrong_key); + + torture_rpc_tcase_add_test(tcase, "server_wrap_encrypt_decrypt_wrong_sid", + test_ServerWrap_encrypt_decrypt_wrong_sid); + + return suite; +} diff --git a/source4/torture/rpc/bench.c b/source4/torture/rpc/bench.c new file mode 100644 index 0000000..88825c5 --- /dev/null +++ b/source4/torture/rpc/bench.c @@ -0,0 +1,152 @@ +/* + Unix SMB/CIFS implementation. + + simple RPC benchmark + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_srvsvc_c.h" +#include "torture/rpc/torture_rpc.h" + +/**************************/ +/* srvsvc_NetShare */ +/**************************/ +static bool test_NetShareEnumAll(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + struct srvsvc_NetShareEnumAll r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr0 c0; + struct srvsvc_NetShareCtr1 c1; + struct srvsvc_NetShareCtr2 c2; + struct srvsvc_NetShareCtr501 c501; + struct srvsvc_NetShareCtr502 c502; + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1, 2, 501, 502}; + int i; + bool ret = true; + uint32_t resume_handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(mem_ctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + r.out.resume_handle = &resume_handle; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + resume_handle = 0; + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 501: + ZERO_STRUCT(c501); + info_ctr.ctr.ctr501 = &c501; + break; + case 502: + ZERO_STRUCT(c502); + info_ctr.ctr.ctr502 = &c502; + break; + } + + status = dcerpc_srvsvc_NetShareEnumAll_r(b, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + printf("NetShareEnumAll level %u failed - %s\n", info_ctr.level, nt_errstr(status)); + ret = false; + continue; + } + if (!W_ERROR_IS_OK(r.out.result)) { + printf("NetShareEnumAll level %u failed - %s\n", info_ctr.level, win_errstr(r.out.result)); + continue; + } + } + + return ret; +} + +/* + benchmark srvsvc netshareenumall queries +*/ +static bool bench_NetShareEnumAll(struct torture_context *tctx, struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx) +{ + struct timeval tv = timeval_current(); + bool ret = true; + int timelimit = torture_setting_int(tctx, "timelimit", 10); + int count=0; + + printf("Running for %d seconds\n", timelimit); + while (timeval_elapsed(&tv) < timelimit) { + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!test_NetShareEnumAll(p, tmp_ctx)) break; + talloc_free(tmp_ctx); + count++; + if (count % 50 == 0) { + if (torture_setting_bool(tctx, "progress", true)) { + printf("%.1f queries per second \r", + count / timeval_elapsed(&tv)); + } + } + } + + printf("%.1f queries per second \n", count / timeval_elapsed(&tv)); + + return ret; +} + + +bool torture_bench_rpc(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + TALLOC_CTX *mem_ctx; + bool ret = true; + + mem_ctx = talloc_init("torture_rpc_srvsvc"); + + status = torture_rpc_connection(torture, + &p, + &ndr_table_srvsvc); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return false; + } + + if (!bench_NetShareEnumAll(torture, p, mem_ctx)) { + ret = false; + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/source4/torture/rpc/bind.c b/source4/torture/rpc/bind.c new file mode 100644 index 0000000..7c0d5e4 --- /dev/null +++ b/source4/torture/rpc/bind.c @@ -0,0 +1,245 @@ +/* + Unix SMB/CIFS implementation. + test suite for rpc bind operations + + Copyright (C) Guenther Deschner 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_epmapper_c.h" +#include "lib/cmdline/cmdline.h" + +static bool test_openpolicy(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle *handle; + + torture_assert(tctx, + test_lsa_OpenPolicy2(b, tctx, &handle), + "failed to open policy"); + + torture_assert(tctx, + test_lsa_Close(b, tctx, handle), + "failed to close policy"); + + return true; +} + +static bool test_bind(struct torture_context *tctx, + const void *private_data) +{ + struct dcerpc_binding *binding; + struct dcerpc_pipe *p; + NTSTATUS status; + const uint32_t *flags = (const uint32_t *)private_data; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding), + "failed to parse binding string"); + + status = dcerpc_binding_set_flags(binding, *flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p, binding, + &ndr_table_lsarpc, + samba_cmdline_get_creds(), + tctx->ev, + tctx->lp_ctx), + "failed to connect pipe"); + + torture_assert(tctx, + test_openpolicy(tctx, p), + "failed to test openpolicy"); + + talloc_free(p); + + return true; +} + +/** + * Verifies a handle created in a connection is available on + * a second connection when the same association group is + * requested in the bind operation. The LSA interface can't be + * used because it runs in preforking mode in the selftests. + * Association groups should work when binding to interfaces + * running in the same process. + */ +static bool test_assoc_group_handles_external(struct torture_context *tctx, + const void *private_data) +{ + struct dcerpc_binding *binding1 = NULL; + struct dcerpc_binding *binding2 = NULL; + struct dcerpc_pipe *p1 = NULL; + struct dcerpc_pipe *p2 = NULL; + struct epm_Lookup r; + struct epm_LookupHandleFree f; + struct policy_handle handle; + uint32_t assoc_group_id; + uint32_t num_ents = 0; + + ZERO_STRUCT(handle); + + /* Open first pipe and open a policy handle */ + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding1), + "failed to parse binding string"); + dcerpc_binding_set_transport(binding1, NCACN_IP_TCP); + dcerpc_binding_set_string_option(binding1, "endpoint", "135"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p1, binding1, + &ndr_table_epmapper, + samba_cmdline_get_creds(), + tctx->ev, + tctx->lp_ctx), + "failed to connect first pipe"); + + r.in.inquiry_type = RPC_C_EP_ALL_ELTS; + r.in.object = NULL; + r.in.interface_id = NULL; + r.in.vers_option = RPC_C_VERS_ALL; + + r.in.entry_handle = &handle; + r.in.max_ents = 1; + + r.out.entry_handle = &handle; + r.out.num_ents = &num_ents; + + torture_assert_ntstatus_ok(tctx, + dcerpc_epm_Lookup_r(p1->binding_handle, tctx, &r), + "failed EPM Lookup"); + torture_assert_int_equal(tctx, + r.out.result, + EPMAPPER_STATUS_OK, + "failed EPM Lookup"); + + /* Open second pipe, different association group. Handle not found */ + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding2), + "failed to parse binding string"); + dcerpc_binding_set_transport(binding2, NCACN_IP_TCP); + dcerpc_binding_set_string_option(binding2, "endpoint", "135"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, binding2, + &ndr_table_epmapper, + samba_cmdline_get_creds(), + tctx->ev, + tctx->lp_ctx), + "failed to connect second pipe"); + + torture_assert_ntstatus_equal(tctx, + dcerpc_epm_Lookup_r(p2->binding_handle, tctx, &r), + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "Unexpected EPM Lookup success"); + + /* Open second pipe, same association group. Handle is found */ + assoc_group_id = dcerpc_binding_get_assoc_group_id(p1->binding); + dcerpc_binding_set_assoc_group_id(binding2, assoc_group_id); + + TALLOC_FREE(p2); + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, binding2, + &ndr_table_epmapper, + samba_cmdline_get_creds(), + tctx->ev, + tctx->lp_ctx), + "failed to connect second pipe"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_epm_Lookup_r(p2->binding_handle, tctx, &r), + "failed EPM Lookup"); + + torture_assert_int_equal(tctx, + r.out.result, + EPMAPPER_STATUS_OK, + "failed EPM Lookup"); + + /* Cleanup */ + f.in.entry_handle = &handle; + f.out.entry_handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_epm_LookupHandleFree_r(p1->binding_handle, tctx, &f), + "failed EPM LookupHandleFree"); + + torture_assert_int_equal(tctx, + r.out.result, + EPMAPPER_STATUS_OK, + "failed EPM LookupHandleFree"); + + TALLOC_FREE(p1); + TALLOC_FREE(p2); + TALLOC_FREE(binding2); + TALLOC_FREE(binding1); + + return true; +} + +static void test_bind_op(struct torture_suite *suite, + const char *name, + uint32_t flags) +{ + uint32_t *flags_p = talloc(suite, uint32_t); + + *flags_p = flags; + + torture_suite_add_simple_tcase_const(suite, name, test_bind, flags_p); +} + + +struct torture_suite *torture_rpc_bind(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "bind"); + struct { + const char *test_name; + uint32_t flags; + } tests[] = { + { + .test_name = "ntlm,sign", + .flags = DCERPC_AUTH_NTLM | DCERPC_SIGN + },{ + .test_name = "ntlm,sign,seal", + .flags = DCERPC_AUTH_NTLM | DCERPC_SIGN | DCERPC_SEAL + },{ + .test_name = "spnego,sign", + .flags = DCERPC_AUTH_SPNEGO | DCERPC_SIGN + },{ + .test_name = "spnego,sign,seal", + .flags = DCERPC_AUTH_SPNEGO | DCERPC_SIGN | DCERPC_SEAL + } + }; + int i; + + for (i=0; i < ARRAY_SIZE(tests); i++) { + test_bind_op(suite, tests[i].test_name, tests[i].flags); + } + for (i=0; i < ARRAY_SIZE(tests); i++) { + test_bind_op(suite, talloc_asprintf(suite, "bigendian,%s", tests[i].test_name), tests[i].flags | DCERPC_PUSH_BIGENDIAN); + } + + torture_suite_add_simple_tcase_const(suite, + "assoc_group_handles_external", + test_assoc_group_handles_external, + NULL); + + return suite; +} diff --git a/source4/torture/rpc/browser.c b/source4/torture/rpc/browser.c new file mode 100644 index 0000000..2fbcd4e --- /dev/null +++ b/source4/torture/rpc/browser.c @@ -0,0 +1,124 @@ +/* + Unix SMB/CIFS implementation. + + test suite for browser rpc operations + + 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 "librpc/gen_ndr/ndr_browser_c.h" +#include "torture/rpc/torture_rpc.h" + +bool test_BrowserrQueryOtherDomains(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct BrowserrQueryOtherDomains r; + struct BrowserrSrvInfo info; + struct BrowserrSrvInfo100Ctr ctr100; + struct srvsvc_NetSrvInfo100 entries100[1]; + struct BrowserrSrvInfo101Ctr ctr101; + struct srvsvc_NetSrvInfo101 entries101[1]; + uint32_t total_entries; + NTSTATUS status; + + torture_comment(tctx, "dcerpc_BrowserrQueryOtherDomains\n"); + + ZERO_STRUCT(r); + ZERO_STRUCT(info); + ZERO_STRUCT(ctr100); + ZERO_STRUCT(entries100); + ZERO_STRUCT(ctr101); + ZERO_STRUCT(entries101); + total_entries = 0; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info = &info; + r.out.info = &info; + r.out.total_entries = &total_entries; + + info.level = 100; + info.info.info100 = &ctr100; + + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_ok(tctx, r.out.result, "BrowserrQueryOtherDomains failed"); + torture_assert_int_equal(tctx, *r.out.total_entries, 0, "BrowserrQueryOtherDomains"); + + info.info.info100 = &ctr100; + ctr100.entries_read = ARRAY_SIZE(entries100); + ctr100.entries = entries100; + + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_ok(tctx, r.out.result, "BrowserrQueryOtherDomains failed"); + torture_assert_int_equal(tctx, *r.out.total_entries, 0, "BrowserrQueryOtherDomains"); + + info.info.info100 = NULL; + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_PARAMETER, r.out.result, + "BrowserrQueryOtherDomains failed"); + + info.level = 101; + info.info.info101 = &ctr101; + + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_LEVEL, r.out.result, + "BrowserrQueryOtherDomains"); + + info.info.info101 = &ctr101; + ctr101.entries_read = ARRAY_SIZE(entries101); + ctr101.entries = entries101; + + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_LEVEL, r.out.result, + "BrowserrQueryOtherDomains"); + + info.info.info101 = NULL; + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_LEVEL, r.out.result, + "BrowserrQueryOtherDomains"); + + info.level = 102; + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_LEVEL, r.out.result, + "BrowserrQueryOtherDomains"); + + info.level = 0; + status = dcerpc_BrowserrQueryOtherDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "BrowserrQueryOtherDomains failed"); + torture_assert_werr_equal(tctx, WERR_INVALID_LEVEL, r.out.result, + "BrowserrQueryOtherDomains"); + + return true; +} + +struct torture_suite *torture_rpc_browser(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "browser"); + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, "browser", &ndr_table_browser); + + torture_rpc_tcase_add_test(tcase, "BrowserrQueryOtherDomains", test_BrowserrQueryOtherDomains); + + return suite; +} + diff --git a/source4/torture/rpc/clusapi.c b/source4/torture/rpc/clusapi.c new file mode 100644 index 0000000..99a272d --- /dev/null +++ b/source4/torture/rpc/clusapi.c @@ -0,0 +1,4185 @@ +/* + Unix SMB/CIFS implementation. + test suite for clusapi rpc operations + + Copyright (C) Günther Deschner 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 "librpc/gen_ndr/ndr_clusapi_c.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "libcli/registry/util_reg.h" + +struct torture_clusapi_context { + struct dcerpc_pipe *p; + const char *NodeName; + const char *ClusterName; + uint16_t lpwMajorVersion; + uint16_t lpwMinorVersion; + uint16_t lpwBuildNumber; +}; + +static bool test_OpenCluster_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenCluster r; + WERROR Status; + + r.out.Status = &Status; + r.out.Cluster = Cluster; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenCluster_r(b, tctx, &r), + "OpenCluster failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenCluster failed"); + + return true; +} + +static bool test_OpenClusterEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenClusterEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.Status = &Status; + r.out.hCluster = Cluster; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenClusterEx_r(b, tctx, &r), + "OpenClusterEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenClusterEx failed"); + + return true; +} + +static bool test_CloseCluster_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseCluster r; + + r.in.Cluster = Cluster; + r.out.Cluster = Cluster; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseCluster_r(b, tctx, &r), + "CloseCluster failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseCluster failed"); + + torture_assert(tctx, + ndr_policy_handle_empty(Cluster), + "policy_handle non empty after CloseCluster"); + + return true; +} + +static bool test_OpenCluster(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + test_CloseCluster_int(tctx, t->p, &Cluster); + + return true; +} + +static bool test_OpenClusterEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + + if (!test_OpenClusterEx_int(tctx, t->p, &Cluster)) { + return false; + } + + test_CloseCluster_int(tctx, t->p, &Cluster); + + return true; +} + +static bool test_CloseCluster(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + return test_CloseCluster_int(tctx, t->p, &Cluster); +} + +static bool test_GetClusterName_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char **ClusterName) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetClusterName r; + const char *NodeName; + + r.out.ClusterName = ClusterName; + r.out.NodeName = &NodeName; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetClusterName_r(b, tctx, &r), + "GetClusterName failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetClusterName failed"); + + return true; +} + +static bool test_SetClusterName(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_SetClusterName r; + const char *NewClusterName; + WERROR rpc_status; + + torture_assert(tctx, + test_GetClusterName_int(tctx, t->p, &NewClusterName), + "failed to query old ClusterName"); + + r.in.NewClusterName = NewClusterName; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_SetClusterName_r(b, tctx, &r), + "SetClusterName failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_RESOURCE_PROPERTIES_STORED, + "SetClusterName failed"); + + return true; +} + +static bool test_GetClusterName(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + const char *ClusterName; + + return test_GetClusterName_int(tctx, t->p, &ClusterName); +} + +static bool test_GetClusterVersion(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_GetClusterVersion r; + uint16_t lpwMajorVersion; + uint16_t lpwMinorVersion; + uint16_t lpwBuildNumber; + const char *lpszVendorId; + const char *lpszCSDVersion; + + r.out.lpwMajorVersion = &lpwMajorVersion; + r.out.lpwMinorVersion = &lpwMinorVersion; + r.out.lpwBuildNumber = &lpwBuildNumber; + r.out.lpszVendorId = &lpszVendorId; + r.out.lpszCSDVersion = &lpszCSDVersion; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetClusterVersion_r(b, tctx, &r), + "GetClusterVersion failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CALL_NOT_IMPLEMENTED, + "GetClusterVersion failed"); + + return true; +} + +static bool test_GetClusterVersion2(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_GetClusterVersion2 r; + uint16_t lpwMajorVersion; + uint16_t lpwMinorVersion; + uint16_t lpwBuildNumber; + const char *lpszVendorId; + const char *lpszCSDVersion; + struct CLUSTER_OPERATIONAL_VERSION_INFO *ppClusterOpVerInfo; + WERROR rpc_status; + + r.out.lpwMajorVersion = &lpwMajorVersion; + r.out.lpwMinorVersion = &lpwMinorVersion; + r.out.lpwBuildNumber = &lpwBuildNumber; + r.out.lpszVendorId = &lpszVendorId; + r.out.lpszCSDVersion = &lpszCSDVersion; + r.out.ppClusterOpVerInfo = &ppClusterOpVerInfo; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetClusterVersion2_r(b, tctx, &r), + "GetClusterVersion2 failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetClusterVersion2 failed"); + + return true; +} + +static bool test_CreateEnum(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType[] = { + CLUSTER_ENUM_NODE, + CLUSTER_ENUM_RESTYPE, + CLUSTER_ENUM_RESOURCE, + CLUSTER_ENUM_GROUP, + CLUSTER_ENUM_NETWORK, + CLUSTER_ENUM_NETINTERFACE, + CLUSTER_ENUM_INTERNAL_NETWORK, + CLUSTER_ENUM_SHARED_VOLUME_RESOURCE + }; + uint32_t dwType_invalid[] = { + 0x00000040, + 0x00000080, + 0x00000100 /* and many more ... */ + }; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + for (i=0; i < ARRAY_SIZE(dwType); i++) { + + r.in.dwType = dwType[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + } + + for (i=0; i < ARRAY_SIZE(dwType_invalid); i++) { + + r.in.dwType = dwType_invalid[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "CreateEnum failed"); + } + + return true; +} + +static bool test_CreateEnumEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CreateEnumEx r; + uint32_t dwType[] = { + CLUSTER_ENUM_NODE, + CLUSTER_ENUM_RESTYPE, + CLUSTER_ENUM_RESOURCE, + CLUSTER_ENUM_GROUP, + CLUSTER_ENUM_NETWORK, + CLUSTER_ENUM_NETINTERFACE, + CLUSTER_ENUM_INTERNAL_NETWORK, + CLUSTER_ENUM_SHARED_VOLUME_RESOURCE + }; + uint32_t dwType_invalid[] = { + 0x00000040, + 0x00000080, + 0x00000100 /* and many more ... */ + }; + struct ENUM_LIST *ReturnIdEnum; + struct ENUM_LIST *ReturnNameEnum; + WERROR rpc_status; + int i; + + for (i=0; i < ARRAY_SIZE(dwType); i++) { + + r.in.hCluster = *Cluster; + r.in.dwType = dwType[i]; + r.in.dwOptions = 0; + r.out.ReturnIdEnum = &ReturnIdEnum; + r.out.ReturnNameEnum = &ReturnNameEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnumEx_r(b, tctx, &r), + "CreateEnumEx failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnumEx failed"); + } + + for (i=0; i < ARRAY_SIZE(dwType_invalid); i++) { + + r.in.hCluster = *Cluster; + r.in.dwType = dwType_invalid[i]; + r.in.dwOptions = 0; + r.out.ReturnIdEnum = &ReturnIdEnum; + r.out.ReturnNameEnum = &ReturnNameEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnumEx_r(b, tctx, &r), + "CreateEnumEx failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "CreateEnumEx failed"); + } + + return true; +} + +static bool test_CreateEnumEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + bool ret; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + ret = test_CreateEnumEx_int(tctx, t->p, &Cluster); + + test_CloseCluster_int(tctx, t->p, &Cluster); + + return ret; +} + + +static bool test_GetQuorumResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_GetQuorumResource r; + const char *lpszResourceName; + const char *lpszDeviceName; + uint32_t pdwMaxQuorumLogSize; + WERROR rpc_status; + + r.out.lpszResourceName = &lpszResourceName; + r.out.lpszDeviceName = &lpszDeviceName; + r.out.pdwMaxQuorumLogSize = &pdwMaxQuorumLogSize; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetQuorumResource_r(b, tctx, &r), + "GetQuorumResource failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetQuorumResource failed"); + + return true; +} + +static bool test_SetQuorumResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_SetQuorumResource r; + const char *lpszDeviceName = ""; + uint32_t dwMaxQuorumLogSize = 0; + WERROR rpc_status; + struct policy_handle hResource; + + /* we need to figure out how this call works and what we provide as + devicename and resource handle - gd + */ + + torture_skip(tctx, "skipping SetQuorumResource test"); + + ZERO_STRUCT(hResource); + + r.in.hResource = hResource; + r.in.lpszDeviceName = lpszDeviceName; + r.in.dwMaxQuorumLogSize = dwMaxQuorumLogSize; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_SetQuorumResource_r(b, tctx, &r), + "SetQuorumResource failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "SetQuorumResource failed"); + + return true; +} + +static bool test_OpenResource_int_exp(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszResourceName, + struct policy_handle *hResource, + WERROR expected_Status, + WERROR expected_rpc_status) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenResource r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszResourceName = lpszResourceName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hResource = hResource; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenResource_r(b, tctx, &r), + "OpenResource failed"); + torture_assert_werr_equal(tctx, + *r.out.Status, expected_Status, + "OpenResource failed"); + torture_assert_werr_equal(tctx, + *r.out.rpc_status, expected_rpc_status, + "OpenResource failed"); + + return true; +} + +bool test_OpenResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszResourceName, + struct policy_handle *hResource) +{ + return test_OpenResource_int_exp(tctx, p, + lpszResourceName, + hResource, + WERR_OK, WERR_OK); +} + +static bool test_OpenResourceEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszResourceName, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenResourceEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + WERROR rpc_status; + + r.in.lpszResourceName = lpszResourceName; + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hResource = hResource; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenResourceEx_r(b, tctx, &r), + "OpenResourceEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenResourceEx failed"); + + return true; +} + +bool test_CloseResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseResource r; + + r.in.Resource = hResource; + r.out.Resource = hResource; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseResource_r(b, tctx, &r), + "CloseResource failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseResource failed"); + torture_assert(tctx, + ndr_policy_handle_empty(hResource), + "policy_handle non empty after CloseResource"); + + return true; +} + +static bool test_OpenResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + test_CloseResource_int(tctx, t->p, &hResource); + + if (!test_OpenResource_int_exp(tctx, t->p, "", &hResource, WERR_RESOURCE_NOT_FOUND, WERR_OK)) { + return false; + } + + torture_assert(tctx, + ndr_policy_handle_empty(&hResource), + "expected empty policy handle"); + + if (!test_OpenResource_int_exp(tctx, t->p, "jfUF38fjSNcfn", &hResource, WERR_RESOURCE_NOT_FOUND, WERR_OK)) { + return false; + } + + torture_assert(tctx, + ndr_policy_handle_empty(&hResource), + "expected empty policy handle"); + + return true; +} + +static bool test_OpenResourceEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + + if (!test_OpenResourceEx_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + test_CloseResource_int(tctx, t->p, &hResource); + + return true; +} + + +static bool test_CloseResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + return test_CloseResource_int(tctx, t->p, &hResource); +} + +static bool test_OpenGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszGroupName, + struct policy_handle *hGroup); +static bool test_CloseGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Group); + +static bool test_CreateResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CreateResource r; + const char *lpszResourceName = "wurst"; + const char *lpszResourceType = "Generic Service"; + WERROR Status; + WERROR rpc_status; + struct policy_handle hGroup; + + torture_assert(tctx, + test_OpenGroup_int(tctx, p, "Cluster Group", &hGroup), + "failed to open group"); + + r.in.hGroup = hGroup; + r.in.lpszResourceName = lpszResourceName; + r.in.lpszResourceType = lpszResourceType; + r.in.dwFlags = CLUSTER_RESOURCE_DEFAULT_MONITOR; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hResource = hResource; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResource_r(b, tctx, &r), + "CreateResource failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "CreateResource failed"); + + test_CloseGroup_int(tctx, p, &hGroup); + + return true; +} + +static bool test_DeleteResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_DeleteResource r; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_DeleteResource_r(b, tctx, &r), + "DeleteResource failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "DeleteResource failed"); + + return true; +} + +static bool test_CreateResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + + if (!test_CreateResource_int(tctx, t->p, &hResource)) { + return false; + } + + test_DeleteResource_int(tctx, t->p, &hResource); + + return true; +} + +static bool test_DeleteResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + + if (!test_CreateResource_int(tctx, t->p, &hResource)) { + return false; + } + + return test_DeleteResource_int(tctx, t->p, &hResource); +} + +static bool test_SetResourceName_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_SetResourceName r; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.in.lpszResourceName = "wurst"; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_SetResourceName_r(b, tctx, &r), + "SetResourceName failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "SetResourceName failed"); + + return true; +} + +static bool test_SetResourceName(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_CreateResource_int(tctx, t->p, &hResource)) { + return false; + } + + ret = test_SetResourceName_int(tctx, t->p, &hResource); + + test_DeleteResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_GetResourceState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceState r; + enum clusapi_ClusterResourceState State; + const char *NodeName; + const char *GroupName; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.State = &State; + r.out.NodeName = &NodeName; + r.out.GroupName = &GroupName; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceState_r(b, tctx, &r), + "GetResourceState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceState failed"); + + return true; +} + +static bool test_GetResourceState(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_GetResourceState_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_GetResourceId_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceId r; + const char *pGuid; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.pGuid = &pGuid; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceId_r(b, tctx, &r), + "GetResourceId failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceId failed"); + + return true; +} + +static bool test_GetResourceId(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_GetResourceId_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_GetResourceType_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceType r; + const char *lpszResourceType; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.lpszResourceType = &lpszResourceType; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceType_r(b, tctx, &r), + "GetResourceType failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceType failed"); + + return true; +} + +static bool test_GetResourceType(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_GetResourceType_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_FailResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_FailResource r; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_FailResource_r(b, tctx, &r), + "FailResource failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "FailResource failed"); + + return true; +} + +static bool test_FailResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_FailResource_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +bool test_OnlineResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OnlineResource r; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OnlineResource_r(b, tctx, &r), + "OnlineResource failed"); + if (!W_ERROR_IS_OK(r.out.result) && + !W_ERROR_EQUAL(r.out.result, WERR_IO_PENDING)) { + torture_result(tctx, TORTURE_FAIL, + "OnlineResource failed with %s", + win_errstr(r.out.result)); + return false; + } + + return true; +} + +static bool test_OnlineResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_OnlineResource_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +bool test_OfflineResource_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OfflineResource r; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OfflineResource_r(b, tctx, &r), + "OfflineResource failed"); + if (!W_ERROR_IS_OK(r.out.result) && + !W_ERROR_EQUAL(r.out.result, WERR_IO_PENDING)) { + torture_result(tctx, TORTURE_FAIL, + "OfflineResource failed with %s", + win_errstr(r.out.result)); + return false; + } + + return true; +} + +static bool test_OfflineResource(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_OfflineResource_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_CreateResEnum_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CreateResEnum r; + uint32_t dwType = CLUSTER_ENUM_RESOURCE; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResEnum_r(b, tctx, &r), + "CreateResEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateResEnum failed"); + + return true; +} + +static bool test_CreateResEnum(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_CreateResEnum_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_GetResourceDependencyExpression_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceDependencyExpression r; + const char *lpszDependencyExpression; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.lpszDependencyExpression = &lpszDependencyExpression; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceDependencyExpression_r(b, tctx, &r), + "GetResourceDependencyExpression failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceDependencyExpression failed"); + + return true; +} + +static bool test_GetResourceDependencyExpression(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Cluster Name", &hResource)) { + return false; + } + + ret = test_GetResourceDependencyExpression_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_GetResourceNetworkName_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceNetworkName r; + const char *lpszName; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.lpszName = &lpszName; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceNetworkName_r(b, tctx, &r), + "GetResourceNetworkName failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceNetworkName failed"); + + return true; +} + +static bool test_GetResourceNetworkName(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hResource; + bool ret = true; + + if (!test_OpenResource_int(tctx, t->p, "Network Name", &hResource)) { + return false; + } + + ret = test_GetResourceNetworkName_int(tctx, t->p, &hResource); + + test_CloseResource_int(tctx, t->p, &hResource); + + return ret; +} + +static bool test_ResourceTypeControl_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster, + const char *resource_type, + enum clusapi_ResourceTypeControlCode dwControlCode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_ResourceTypeControl r; + uint32_t lpBytesReturned; + uint32_t lpcbRequired; + WERROR rpc_status; + + r.in.hCluster = *Cluster; + r.in.lpszResourceTypeName = resource_type; + r.in.dwControlCode = 0; + r.in.lpInBuffer = NULL; + r.in.nInBufferSize = 0; + r.in.nOutBufferSize = 0; + r.out.lpOutBuffer = NULL; + r.out.lpBytesReturned = &lpBytesReturned; + r.out.lpcbRequired = &lpcbRequired; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ResourceTypeControl_r(b, tctx, &r), + "ResourceTypeControl failed"); + + if (strequal(r.in.lpszResourceTypeName, "MSMQ") || + strequal(r.in.lpszResourceTypeName, "MSMQTriggers")) { + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CLUSTER_RESTYPE_NOT_SUPPORTED, + "ResourceTypeControl failed"); + return true; + } + + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_FUNCTION, + "ResourceTypeControl failed"); + + r.in.dwControlCode = dwControlCode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ResourceTypeControl_r(b, tctx, &r), + "ResourceTypeControl failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, *r.out.lpcbRequired); + r.in.nOutBufferSize = *r.out.lpcbRequired; + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ResourceTypeControl_r(b, tctx, &r), + "ResourceTypeControl failed"); + } + torture_assert_werr_ok(tctx, + r.out.result, + "ResourceTypeControl failed"); + + /* now try what happens when we query with a buffer large enough to hold + * the entire packet */ + + r.in.nOutBufferSize = 0x4000; + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, r.in.nOutBufferSize); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ResourceTypeControl_r(b, tctx, &r), + "ResourceTypeControl failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "ResourceTypeControl failed"); + torture_assert(tctx, *r.out.lpBytesReturned < r.in.nOutBufferSize, + "lpBytesReturned expected to be smaller than input size nOutBufferSize"); + + return true; +} + +static bool test_ResourceTypeControl(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *resourcetype_name) +{ + struct policy_handle Cluster; + bool ret; + uint32_t control_codes[] = { + CLUSCTL_RESOURCE_TYPE_GET_CLASS_INFO, + CLUSCTL_RESOURCE_TYPE_GET_CHARACTERISTICS, + CLUSCTL_RESOURCE_TYPE_GET_COMMON_PROPERTIES, + CLUSCTL_RESOURCE_TYPE_GET_RO_COMMON_PROPERTIES, + CLUSCTL_RESOURCE_TYPE_GET_PRIVATE_PROPERTIES + }; + int i; + + if (!test_OpenCluster_int(tctx, p, &Cluster)) { + return false; + } + + for (i=0; i < ARRAY_SIZE(control_codes); i++) { + ret = test_ResourceTypeControl_int(tctx, p, &Cluster, + resourcetype_name, + control_codes[i]); + if (!ret) { + goto done; + } + } + + done: + test_CloseCluster_int(tctx, p, &Cluster); + + return ret; +} + + + +static bool test_one_resourcetype(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *resourcetype_name) +{ + torture_assert(tctx, + test_ResourceTypeControl(tctx, p, resourcetype_name), + "failed to query ResourceTypeControl"); + + return true; +} + +static bool test_one_resource(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *resource_name) +{ + struct policy_handle hResource; + + torture_assert(tctx, + test_OpenResource_int(tctx, p, resource_name, &hResource), + "failed to open resource"); + test_CloseResource_int(tctx, p, &hResource); + + torture_assert(tctx, + test_OpenResourceEx_int(tctx, p, resource_name, &hResource), + "failed to openex resource"); + + torture_assert(tctx, + test_GetResourceType_int(tctx, p, &hResource), + "failed to query resource type"); + torture_assert(tctx, + test_GetResourceId_int(tctx, p, &hResource), + "failed to query resource id"); + torture_assert(tctx, + test_GetResourceState_int(tctx, p, &hResource), + "failed to query resource state"); + torture_assert(tctx, + test_CreateResEnum_int(tctx, p, &hResource), + "failed to query resource enum"); + torture_assert(tctx, + test_GetResourceDependencyExpression_int(tctx, p, &hResource), + "failed to query resource dependency expression"); + torture_assert(tctx, + test_GetResourceNetworkName_int(tctx, p, &hResource), + "failed to query resource network name"); + + test_CloseResource_int(tctx, p, &hResource); + + return true; +} + +static bool test_all_resources(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_RESOURCE; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_RESOURCE, "type mismatch"); + + torture_assert(tctx, + test_one_resource(tctx, t->p, e.Name), + "failed to test one resource"); + } + + return true; +} + +static bool test_all_resourcetypes(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_RESTYPE; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_RESTYPE, "type mismatch"); + + torture_assert(tctx, + test_one_resourcetype(tctx, t->p, e.Name), + "failed to test one resourcetype"); + } + + return true; +} + + +static bool test_OpenNode_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNodeName, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNode r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNodeName = lpszNodeName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNode= hNode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNode_r(b, tctx, &r), + "OpenNode failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNode failed"); + + return true; +} + +static bool test_OpenNodeEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNodeName, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNodeEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNodeName = lpszNodeName; + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNode= hNode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNodeEx_r(b, tctx, &r), + "OpenNodeEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNodeEx failed"); + + return true; +} + + +static bool test_CloseNode_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Node) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseNode r; + + r.in.Node = Node; + r.out.Node = Node; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseNode_r(b, tctx, &r), + "CloseNode failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseNode failed"); + torture_assert(tctx, + ndr_policy_handle_empty(Node), + "policy_handle non empty after CloseNode"); + + return true; +} + +static bool test_OpenNode(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + test_CloseNode_int(tctx, t->p, &hNode); + + return true; +} + +static bool test_OpenNodeEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + + if (!test_OpenNodeEx_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + test_CloseNode_int(tctx, t->p, &hNode); + + return true; +} + +static bool test_CloseNode(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + return test_CloseNode_int(tctx, t->p, &hNode); +} + +static bool test_GetNodeState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNodeState r; + enum clusapi_ClusterNodeState State; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.out.State = &State; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNodeState_r(b, tctx, &r), + "GetNodeState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNodeState failed"); + + return true; +} + +static bool test_GetNodeState(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_GetNodeState_int(tctx, t->p, &hNode); + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_GetNodeId_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNodeId r; + const char *pGuid; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.out.pGuid = &pGuid; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNodeId_r(b, tctx, &r), + "GetNodeId failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNodeId failed"); + + return true; +} + +static bool test_GetNodeId(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_GetNodeId_int(tctx, t->p, &hNode); + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_NodeControl_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode, + enum clusapi_NodeControlCode dwControlCode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_NodeControl r; + uint32_t lpBytesReturned; + uint32_t lpcbRequired; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.in.dwControlCode = 0; + r.in.lpInBuffer = NULL; + r.in.nInBufferSize = 0; + r.in.nOutBufferSize = 0; + r.out.lpOutBuffer = NULL; + r.out.lpBytesReturned = &lpBytesReturned; + r.out.lpcbRequired = &lpcbRequired; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_NodeControl_r(b, tctx, &r), + "NodeControl failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_FUNCTION, + "NodeControl failed"); + + r.in.dwControlCode = dwControlCode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_NodeControl_r(b, tctx, &r), + "NodeControl failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, *r.out.lpcbRequired); + r.in.nOutBufferSize = *r.out.lpcbRequired; + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_NodeControl_r(b, tctx, &r), + "NodeControl failed"); + } + torture_assert_werr_ok(tctx, + r.out.result, + "NodeControl failed"); + + /* now try what happens when we query with a buffer large enough to hold + * the entire packet */ + + r.in.nOutBufferSize = 0x4000; + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, r.in.nOutBufferSize); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_NodeControl_r(b, tctx, &r), + "NodeControl failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "NodeControl failed"); + torture_assert(tctx, *r.out.lpBytesReturned < r.in.nOutBufferSize, + "lpBytesReturned expected to be smaller than input size nOutBufferSize"); + + if (dwControlCode == CLUSCTL_NODE_GET_ID) { + const char *str; + DATA_BLOB blob = data_blob_const(r.out.lpOutBuffer, *r.out.lpBytesReturned); + + torture_assert(tctx, *r.out.lpBytesReturned >= 4, "must be at least 4 bytes long"); + torture_assert(tctx, (*r.out.lpBytesReturned % 2) == 0, "must be a multiple of 2"); + + torture_assert(tctx, + pull_reg_sz(tctx, &blob, &str), + "failed to pull unicode string"); + + torture_comment(tctx, "got this node id: '%s'", str); + } + + return true; +} + +static bool test_NodeControl(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_NodeControl_int(tctx, t->p, &hNode, CLUSCTL_NODE_GET_RO_COMMON_PROPERTIES); + if (!ret) { + return false; + } + + ret = test_NodeControl_int(tctx, t->p, &hNode, CLUSCTL_NODE_GET_ID); + if (!ret) { + return false; + } + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_PauseNode_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_PauseNode r; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_PauseNode_r(b, tctx, &r), + "PauseNode failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "PauseNode failed"); + + return true; +} + +static bool test_PauseNode(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_PauseNode_int(tctx, t->p, &hNode); + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_ResumeNode_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_ResumeNode r; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ResumeNode_r(b, tctx, &r), + "ResumeNode failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CLUSTER_NODE_NOT_PAUSED, + "ResumeNode gave unexpected result"); + + return true; +} + +static bool test_ResumeNode(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_ResumeNode_int(tctx, t->p, &hNode); + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_EvictNode_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_EvictNode r; + WERROR rpc_status; + + r.in.hNode = *hNode; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_EvictNode_r(b, tctx, &r), + "EvictNode failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "EvictNode failed"); + + return true; +} + +static bool test_EvictNode(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNode; + bool ret = true; + + if (!test_OpenNode_int(tctx, t->p, t->NodeName, &hNode)) { + return false; + } + + ret = test_EvictNode_int(tctx, t->p, &hNode); + + test_CloseNode_int(tctx, t->p, &hNode); + + return ret; +} + +static bool test_one_node(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *node_name) +{ + struct policy_handle hNode; + + torture_assert(tctx, + test_OpenNode_int(tctx, p, node_name, &hNode), + "failed to open node"); + test_CloseNode_int(tctx, p, &hNode); + + torture_assert(tctx, + test_OpenNodeEx_int(tctx, p, node_name, &hNode), + "failed to openex node"); + + torture_assert(tctx, + test_GetNodeId_int(tctx, p, &hNode), + "failed to query node id"); + torture_assert(tctx, + test_GetNodeState_int(tctx, p, &hNode), + "failed to query node id"); + + test_CloseNode_int(tctx, p, &hNode); + + return true; +} + +static bool test_all_nodes(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_NODE; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_NODE, "type mismatch"); + + torture_assert(tctx, + test_one_node(tctx, t->p, e.Name), + "failed to test one node"); + } + + return true; +} + +static bool test_OpenGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszGroupName, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenGroup r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszGroupName = lpszGroupName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hGroup= hGroup; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenGroup_r(b, tctx, &r), + "OpenGroup failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenGroup failed"); + + return true; +} + +static bool test_OpenGroupEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszGroupName, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenGroupEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + WERROR rpc_status; + + r.in.lpszGroupName = lpszGroupName; + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hGroup= hGroup; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenGroupEx_r(b, tctx, &r), + "OpenGroupEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenGroupEx failed"); + + return true; +} + +static bool test_CloseGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Group) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseGroup r; + + r.in.Group = Group; + r.out.Group = Group; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseGroup_r(b, tctx, &r), + "CloseGroup failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseGroup failed"); + torture_assert(tctx, + ndr_policy_handle_empty(Group), + "policy_handle non empty after CloseGroup"); + + return true; +} + +static bool test_OpenGroup(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return true; +} + +static bool test_OpenGroupEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + + if (!test_OpenGroupEx_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return true; +} + +static bool test_CloseGroup(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + return test_CloseGroup_int(tctx, t->p, &hGroup); +} + +static bool test_GetGroupState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetGroupState r; + enum clusapi_ClusterGroupState State; + const char *NodeName; + WERROR rpc_status; + + r.in.hGroup = *hGroup; + r.out.State = &State; + r.out.NodeName = &NodeName; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetGroupState_r(b, tctx, &r), + "GetGroupState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetGroupState failed"); + + return true; +} + +static bool test_GetGroupState(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + bool ret = true; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + ret = test_GetGroupState_int(tctx, t->p, &hGroup); + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return ret; +} + +static bool test_GetGroupId_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetGroupId r; + const char *pGuid; + WERROR rpc_status; + + r.in.hGroup = *hGroup; + r.out.pGuid = &pGuid; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetGroupId_r(b, tctx, &r), + "GetGroupId failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetGroupId failed"); + + return true; +} + +static bool test_GetGroupId(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + bool ret = true; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + ret = test_GetGroupId_int(tctx, t->p, &hGroup); + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return ret; +} + +static bool test_GroupControl_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup, + enum clusapi_GroupControlCode dwControlCode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GroupControl r; + uint32_t lpBytesReturned; + uint32_t lpcbRequired; + WERROR rpc_status; + + r.in.hGroup = *hGroup; + r.in.dwControlCode = 0; + r.in.lpInBuffer = NULL; + r.in.nInBufferSize = 0; + r.in.nOutBufferSize = 0; + r.out.lpOutBuffer = NULL; + r.out.lpBytesReturned = &lpBytesReturned; + r.out.lpcbRequired = &lpcbRequired; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GroupControl_r(b, tctx, &r), + "GroupControl failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_FUNCTION, + "GroupControl failed"); + + r.in.dwControlCode = dwControlCode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GroupControl_r(b, tctx, &r), + "GroupControl failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, *r.out.lpcbRequired); + r.in.nOutBufferSize = *r.out.lpcbRequired; + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GroupControl_r(b, tctx, &r), + "GroupControl failed"); + } + torture_assert_werr_ok(tctx, + r.out.result, + "GroupControl failed"); + + /* now try what happens when we query with a buffer large enough to hold + * the entire packet */ + + r.in.nOutBufferSize = 0x400; + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, r.in.nOutBufferSize); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GroupControl_r(b, tctx, &r), + "GroupControl failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GroupControl failed"); + torture_assert(tctx, *r.out.lpBytesReturned < r.in.nOutBufferSize, + "lpBytesReturned expected to be smaller than input size nOutBufferSize"); + + return true; +} + +static bool test_CreateGroupResourceEnum_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CreateGroupResourceEnum r; + uint32_t dwType[] = { + CLUSTER_GROUP_ENUM_CONTAINS, + CLUSTER_GROUP_ENUM_NODES + }; + uint32_t dwType_invalid[] = { + 0x00000040, + 0x00000080, + 0x00000100 /* and many more ... */ + }; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.hGroup = *hGroup; + + for (i=0; i < ARRAY_SIZE(dwType); i++) { + + r.in.hGroup = *hGroup; + r.in.dwType = dwType[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateGroupResourceEnum_r(b, tctx, &r), + "CreateGroupResourceEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateGroupResourceEnum failed"); + } + + for (i=0; i < ARRAY_SIZE(dwType_invalid); i++) { + + r.in.dwType = dwType_invalid[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateGroupResourceEnum_r(b, tctx, &r), + "CreateGroupResourceEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateGroupResourceEnum failed"); + } + + return true; +} + + +static bool test_GroupControl(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + bool ret = true; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + ret = test_GroupControl_int(tctx, t->p, &hGroup, CLUSCTL_GROUP_GET_CHARACTERISTICS); + if (!ret) { + return false; + } + + ret = test_GroupControl_int(tctx, t->p, &hGroup, CLUSCTL_GROUP_GET_RO_COMMON_PROPERTIES); + if (!ret) { + return false; + } + + ret = test_GroupControl_int(tctx, t->p, &hGroup, CLUSCTL_GROUP_GET_FLAGS); + if (!ret) { + return false; + } + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return ret; +} + +static bool test_OnlineGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OnlineGroup r; + WERROR rpc_status; + + r.in.hGroup = *hGroup; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OnlineGroup_r(b, tctx, &r), + "OnlineGroup failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "OnlineGroup failed"); + + return true; +} + +static bool test_OnlineGroup(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + bool ret = true; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + ret = test_OnlineGroup_int(tctx, t->p, &hGroup); + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return ret; +} + +static bool test_OfflineGroup_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hGroup) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OfflineGroup r; + WERROR rpc_status; + + r.in.hGroup = *hGroup; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OfflineGroup_r(b, tctx, &r), + "OfflineGroup failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "OfflineGroup failed"); + + return true; +} + +static bool test_OfflineGroup(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroup; + bool ret = true; + + if (!test_OpenGroup_int(tctx, t->p, "Cluster Group", &hGroup)) { + return false; + } + + ret = test_OfflineGroup_int(tctx, t->p, &hGroup); + + test_CloseGroup_int(tctx, t->p, &hGroup); + + return ret; +} + +static bool test_one_group(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *group_name) +{ + struct policy_handle hGroup; + + torture_assert(tctx, + test_OpenGroup_int(tctx, p, group_name, &hGroup), + "failed to open group"); + test_CloseGroup_int(tctx, p, &hGroup); + + torture_assert(tctx, + test_OpenGroupEx_int(tctx, p, group_name, &hGroup), + "failed to openex group"); + + torture_assert(tctx, + test_GetGroupId_int(tctx, p, &hGroup), + "failed to query group id"); + torture_assert(tctx, + test_GetGroupState_int(tctx, p, &hGroup), + "failed to query group id"); + + torture_assert(tctx, + test_GroupControl_int(tctx, p, &hGroup, CLUSCTL_GROUP_GET_FLAGS), + "failed to query group control"); + + torture_assert(tctx, + test_CreateGroupResourceEnum_int(tctx, p, &hGroup), + "failed to query resource enum"); + + test_CloseGroup_int(tctx, p, &hGroup); + + return true; +} + +static bool test_all_groups(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_GROUP; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_GROUP, "type mismatch"); + + torture_assert(tctx, + test_one_group(tctx, t->p, e.Name), + "failed to test one group"); + } + + return true; +} + +static bool test_BackupClusterDatabase(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_BackupClusterDatabase r; + WERROR rpc_status; + + r.in.lpszPathName = "c:\\cluster_backup"; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_BackupClusterDatabase_r(b, tctx, &r), + "BackupClusterDatabase failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CALL_NOT_IMPLEMENTED, + "BackupClusterDatabase failed"); + + return true; +} + +static bool test_SetServiceAccountPassword(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_SetServiceAccountPassword r; + uint32_t SizeReturned; + uint32_t ExpectedBufferSize; + + r.in.lpszNewPassword = "P@ssw0rd!"; + r.in.dwFlags = IDL_CLUSTER_SET_PASSWORD_IGNORE_DOWN_NODES; + r.in.ReturnStatusBufferSize = 1024; + r.out.ReturnStatusBufferPtr = NULL; + r.out.SizeReturned = &SizeReturned; + r.out.ExpectedBufferSize = &ExpectedBufferSize; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_SetServiceAccountPassword_r(b, tctx, &r), + "SetServiceAccountPassword failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CALL_NOT_IMPLEMENTED, + "SetServiceAccountPassword failed"); + + return true; +} + +static bool test_ClusterControl_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster, + enum clusapi_ClusterControlCode dwControlCode) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_ClusterControl r; + uint32_t lpBytesReturned; + uint32_t lpcbRequired; + WERROR rpc_status; + + r.in.hCluster = *Cluster; + r.in.dwControlCode = 0; + r.in.lpInBuffer = NULL; + r.in.nInBufferSize = 0; + r.in.nOutBufferSize = 0; + r.out.lpOutBuffer = NULL; + r.out.lpBytesReturned = &lpBytesReturned; + r.out.lpcbRequired = &lpcbRequired; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ClusterControl_r(b, tctx, &r), + "ClusterControl failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_FUNCTION, + "ClusterControl failed"); + + r.in.dwControlCode = dwControlCode; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ClusterControl_r(b, tctx, &r), + "ClusterControl failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, *r.out.lpcbRequired); + r.in.nOutBufferSize = *r.out.lpcbRequired; + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ClusterControl_r(b, tctx, &r), + "ClusterControl failed"); + } + torture_assert_werr_ok(tctx, + r.out.result, + "ClusterControl failed"); + + /* now try what happens when we query with a buffer large enough to hold + * the entire packet */ + + r.in.nOutBufferSize = 0xffff; + r.out.lpOutBuffer = talloc_zero_array(tctx, uint8_t, r.in.nOutBufferSize); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_ClusterControl_r(b, tctx, &r), + "ClusterControl failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "ClusterControl failed"); + torture_assert(tctx, *r.out.lpBytesReturned < r.in.nOutBufferSize, + "lpBytesReturned expected to be smaller than input size nOutBufferSize"); + + return true; +} + +static bool test_ClusterControl(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + bool ret; + uint32_t control_codes[] = { + CLUSCTL_CLUSTER_GET_COMMON_PROPERTIES, + CLUSCTL_CLUSTER_GET_RO_COMMON_PROPERTIES, + CLUSCTL_CLUSTER_GET_FQDN, + CLUSCTL_CLUSTER_GET_PRIVATE_PROPERTIES, + CLUSCTL_CLUSTER_CHECK_VOTER_DOWN + }; + int i; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + for (i=0; i < ARRAY_SIZE(control_codes); i++) { + ret = test_ClusterControl_int(tctx, t->p, &Cluster, + control_codes[i]); + if (!ret) { + goto done; + } + } + + done: + test_CloseCluster_int(tctx, t->p, &Cluster); + + return ret; +} + +static bool test_CreateResTypeEnum(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateResTypeEnum r; + uint32_t dwType[] = { + CLUSTER_RESOURCE_TYPE_ENUM_NODES, + CLUSTER_RESOURCE_TYPE_ENUM_RESOURCES + }; + uint32_t dwType_invalid[] = { + 0x00000040, + 0x00000080, + 0x00000100 /* and many more ... */ + }; + const char *valid_names[] = { + "Physical Disk", + "Storage Pool" + }; + const char *invalid_names[] = { + "INVALID_TYPE_XXXX" + }; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i, s; + + for (s = 0; s < ARRAY_SIZE(valid_names); s++) { + + r.in.lpszTypeName = valid_names[s]; + + for (i=0; i < ARRAY_SIZE(dwType); i++) { + + r.in.dwType = dwType[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResTypeEnum_r(b, tctx, &r), + "CreateResTypeEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateResTypeEnum failed"); + } + + for (i=0; i < ARRAY_SIZE(dwType_invalid); i++) { + + r.in.dwType = dwType_invalid[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResTypeEnum_r(b, tctx, &r), + "CreateResTypeEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateResTypeEnum failed"); + } + } + + for (s = 0; s < ARRAY_SIZE(invalid_names); s++) { + + r.in.lpszTypeName = invalid_names[s]; + + for (i=0; i < ARRAY_SIZE(dwType); i++) { + + r.in.dwType = dwType[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResTypeEnum_r(b, tctx, &r), + "CreateResTypeEnum failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CLUSTER_RESOURCE_TYPE_NOT_FOUND, + "CreateResTypeEnum failed"); + } + + for (i=0; i < ARRAY_SIZE(dwType_invalid); i++) { + + r.in.dwType = dwType_invalid[i]; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateResTypeEnum_r(b, tctx, &r), + "CreateResTypeEnum failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_CLUSTER_RESOURCE_TYPE_NOT_FOUND, + "CreateResTypeEnum failed"); + } + } + + + return true; +} + +static bool test_CreateGroupEnum_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Cluster, + const char **multi_sz, + const char **multi_sz_ro) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CreateGroupEnum r; + struct GROUP_ENUM_LIST *pResultList; + WERROR rpc_status; + DATA_BLOB blob = data_blob_null; + DATA_BLOB blob_ro = data_blob_null; + + r.in.hCluster = *Cluster; + r.in.pProperties = blob.data; + r.in.cbProperties = blob.length; + r.in.pRoProperties = blob_ro.data; + r.in.cbRoProperties = blob_ro.length; + r.out.ppResultList = &pResultList; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateGroupEnum_r(b, tctx, &r), + "CreateGroupEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateGroupEnum failed"); + + if (!push_reg_multi_sz(tctx, &blob, multi_sz)) { + return false; + } + + if (!push_reg_multi_sz(tctx, &blob_ro, multi_sz_ro)) { + return false; + } + + r.in.pProperties = blob.data; + r.in.cbProperties = blob.length; + + r.in.pRoProperties = blob_ro.data; + r.in.cbRoProperties = blob_ro.length; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateGroupEnum_r(b, tctx, &r), + "CreateGroupEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateGroupEnum failed"); + +#if 0 + { + int i; + enum ndr_err_code ndr_err; + + for (i=0; i < pResultList->EntryCount; i++) { + struct clusapi_PROPERTY_LIST list; + torture_comment(tctx, "entry #%d\n", i); + + blob = data_blob_const(pResultList->Entry[i].Properties, + pResultList->Entry[i].cbProperties); + + ndr_err = ndr_pull_struct_blob(&blob, tctx, &list, + (ndr_pull_flags_fn_t)ndr_pull_clusapi_PROPERTY_LIST); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NDR_PRINT_DEBUG(clusapi_PROPERTY_LIST, &list); + } + + blob_ro = data_blob_const(pResultList->Entry[i].RoProperties, + pResultList->Entry[i].cbRoProperties); + + ndr_err = ndr_pull_struct_blob(&blob_ro, tctx, &list, + (ndr_pull_flags_fn_t)ndr_pull_clusapi_PROPERTY_LIST); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NDR_PRINT_DEBUG(clusapi_PROPERTY_LIST, &list); + } + } + } +#endif + + return true; +} + +static bool test_CreateGroupEnum(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle Cluster; + bool ret; + const char *multi_sz[] = { + "Priority", NULL, + }; + const char *multi_sz_ro[] = { + "GroupType", NULL, + }; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + ret = test_CreateGroupEnum_int(tctx, t->p, &Cluster, + multi_sz, multi_sz_ro); + if (!ret) { + goto done; + } + + done: + test_CloseCluster_int(tctx, t->p, &Cluster); + + return ret; +} + +static bool test_OpenNetwork_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNetworkName, + struct policy_handle *hNetwork) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNetwork r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNetworkName = lpszNetworkName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNetwork = hNetwork ; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNetwork_r(b, tctx, &r), + "OpenNetwork failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNetwork failed"); + + return true; +} + +static bool test_OpenNetworkEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNetworkName, + struct policy_handle *hNetwork) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNetworkEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNetworkName = lpszNetworkName; + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNetwork = hNetwork ; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNetworkEx_r(b, tctx, &r), + "OpenNetworkEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNetworkEx failed"); + + return true; +} + +static bool test_CloseNetwork_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *Network) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseNetwork r; + + r.in.Network = Network; + r.out.Network = Network; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseNetwork_r(b, tctx, &r), + "CloseNetwork failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseNetwork failed"); + torture_assert(tctx, + ndr_policy_handle_empty(Network), + "policy_handle non empty after CloseNetwork"); + + return true; +} + +static bool test_OpenNetwork(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetwork; + + if (!test_OpenNetwork_int(tctx, t->p, "Cluster Network 1", &hNetwork)) { + return false; + } + + test_CloseNetwork_int(tctx, t->p, &hNetwork); + + return true; +} + +static bool test_OpenNetworkEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetwork; + + if (!test_OpenNetworkEx_int(tctx, t->p, "Cluster Network 1", &hNetwork)) { + return false; + } + + test_CloseNetwork_int(tctx, t->p, &hNetwork); + + return true; +} + +static bool test_CloseNetwork(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetwork; + + if (!test_OpenNetwork_int(tctx, t->p, "Cluster Network 1", &hNetwork)) { + return false; + } + + return test_CloseNetwork_int(tctx, t->p, &hNetwork); +} + +static bool test_GetNetworkState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNetwork) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNetworkState r; + enum clusapi_ClusterNetworkState State; + WERROR rpc_status; + + r.in.hNetwork = *hNetwork; + r.out.State = &State; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNetworkState_r(b, tctx, &r), + "GetNetworkState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNetworkState failed"); + + return true; +} + +static bool test_GetNetworkState(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetwork; + bool ret = true; + + if (!test_OpenNetwork_int(tctx, t->p, "Cluster Network 1", &hNetwork)) { + return false; + } + + ret = test_GetNetworkState_int(tctx, t->p, &hNetwork); + + test_CloseNetwork_int(tctx, t->p, &hNetwork); + + return ret; +} + +static bool test_GetNetworkId_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNetwork) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNetworkId r; + const char *pGuid; + WERROR rpc_status; + + r.in.hNetwork = *hNetwork; + r.out.pGuid = &pGuid; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNetworkId_r(b, tctx, &r), + "GetNetworkId failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNetworkId failed"); + + return true; +} + +static bool test_GetNetworkId(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetwork; + bool ret = true; + + if (!test_OpenNetwork_int(tctx, t->p, "Cluster Network 1", &hNetwork)) { + return false; + } + + ret = test_GetNetworkId_int(tctx, t->p, &hNetwork); + + test_CloseNetwork_int(tctx, t->p, &hNetwork); + + return ret; +} + +static bool test_one_network(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *network_name) +{ + struct policy_handle hNetwork; + + torture_assert(tctx, + test_OpenNetwork_int(tctx, p, network_name, &hNetwork), + "failed to open network"); + test_CloseNetwork_int(tctx, p, &hNetwork); + + torture_assert(tctx, + test_OpenNetworkEx_int(tctx, p, network_name, &hNetwork), + "failed to openex network"); + + torture_assert(tctx, + test_GetNetworkId_int(tctx, p, &hNetwork), + "failed to query network id"); + torture_assert(tctx, + test_GetNetworkState_int(tctx, p, &hNetwork), + "failed to query network id"); + + test_CloseNetwork_int(tctx, p, &hNetwork); + + return true; +} + +static bool test_all_networks(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_NETWORK; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_NETWORK, "type mismatch"); + + torture_assert(tctx, + test_one_network(tctx, t->p, e.Name), + "failed to test one network"); + } + + return true; +} + +static bool test_OpenNetInterface_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNetInterfaceName, + struct policy_handle *hNetInterface) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNetInterface r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNetInterfaceName = lpszNetInterfaceName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNetInterface = hNetInterface; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNetInterface_r(b, tctx, &r), + "OpenNetInterface failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNetInterface failed"); + + return true; +} + +static bool test_OpenNetInterfaceEx_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszNetInterfaceName, + struct policy_handle *hNetInterface) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenNetInterfaceEx r; + uint32_t lpdwGrantedAccess; + WERROR Status; + WERROR rpc_status; + + r.in.lpszNetInterfaceName = lpszNetInterfaceName; + r.in.dwDesiredAccess = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.lpdwGrantedAccess = &lpdwGrantedAccess; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hNetInterface = hNetInterface; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenNetInterfaceEx_r(b, tctx, &r), + "OpenNetInterfaceEx failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenNetInterfaceEx failed"); + + return true; +} + +static bool test_CloseNetInterface_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *NetInterface) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseNetInterface r; + + r.in.NetInterface = NetInterface; + r.out.NetInterface = NetInterface; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseNetInterface_r(b, tctx, &r), + "CloseNetInterface failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseNetInterface failed"); + torture_assert(tctx, + ndr_policy_handle_empty(NetInterface), + "policy_handle non empty after CloseNetInterface"); + + return true; +} + +static bool test_OpenNetInterface(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetInterface; + + if (!test_OpenNetInterface_int(tctx, t->p, "node1 - Ethernet", &hNetInterface)) { + return false; + } + + test_CloseNetInterface_int(tctx, t->p, &hNetInterface); + + return true; +} + +static bool test_OpenNetInterfaceEx(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetInterface; + + if (!test_OpenNetInterfaceEx_int(tctx, t->p, "node1 - Ethernet", &hNetInterface)) { + return false; + } + + test_CloseNetInterface_int(tctx, t->p, &hNetInterface); + + return true; +} + +static bool test_CloseNetInterface(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetInterface; + + if (!test_OpenNetInterface_int(tctx, t->p, "node1 - Ethernet", &hNetInterface)) { + return false; + } + + return test_CloseNetInterface_int(tctx, t->p, &hNetInterface); +} + +static bool test_GetNetInterfaceState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNetInterface) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNetInterfaceState r; + enum clusapi_ClusterNetInterfaceState State; + WERROR rpc_status; + + r.in.hNetInterface = *hNetInterface; + r.out.State = &State; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNetInterfaceState_r(b, tctx, &r), + "GetNetInterfaceState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNetInterfaceState failed"); + + return true; +} + +static bool test_GetNetInterfaceState(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetInterface; + bool ret = true; + + if (!test_OpenNetInterface_int(tctx, t->p, "node1 - Ethernet", &hNetInterface)) { + return false; + } + + ret = test_GetNetInterfaceState_int(tctx, t->p, &hNetInterface); + + test_CloseNetInterface_int(tctx, t->p, &hNetInterface); + + return ret; +} + +static bool test_GetNetInterfaceId_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hNetInterface) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetNetInterfaceId r; + const char *pGuid; + WERROR rpc_status; + + r.in.hNetInterface = *hNetInterface; + r.out.pGuid = &pGuid; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetNetInterfaceId_r(b, tctx, &r), + "GetNetInterfaceId failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetNetInterfaceId failed"); + + return true; +} + +static bool test_GetNetInterfaceId(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hNetInterface; + bool ret = true; + + if (!test_OpenNetInterface_int(tctx, t->p, "node1 - Ethernet", &hNetInterface)) { + return false; + } + + ret = test_GetNetInterfaceId_int(tctx, t->p, &hNetInterface); + + test_CloseNetInterface_int(tctx, t->p, &hNetInterface); + + return ret; +} + +static bool test_one_netinterface(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *netinterface_name) +{ + struct policy_handle hNetInterface; + + torture_assert(tctx, + test_OpenNetInterface_int(tctx, p, netinterface_name, &hNetInterface), + "failed to open netinterface"); + test_CloseNetInterface_int(tctx, p, &hNetInterface); + + torture_assert(tctx, + test_OpenNetInterfaceEx_int(tctx, p, netinterface_name, &hNetInterface), + "failed to openex netinterface"); + + torture_assert(tctx, + test_GetNetInterfaceId_int(tctx, p, &hNetInterface), + "failed to query netinterface id"); + torture_assert(tctx, + test_GetNetInterfaceState_int(tctx, p, &hNetInterface), + "failed to query netinterface id"); + + test_CloseNetInterface_int(tctx, p, &hNetInterface); + + return true; +} + +static bool test_all_netinterfaces(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateEnum r; + uint32_t dwType = CLUSTER_ENUM_NETINTERFACE; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + int i; + + r.in.dwType = dwType; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "CreateEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateEnum failed"); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert_int_equal(tctx, e.Type, CLUSTER_ENUM_NETINTERFACE, "type mismatch"); + + torture_assert(tctx, + test_one_netinterface(tctx, t->p, e.Name), + "failed to test one netinterface"); + } + + return true; +} + +static bool test_CloseKey_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *pKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseKey r; + + r.in.pKey = pKey; + r.out.pKey = pKey; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseKey_r(b, tctx, &r), + "CloseKey failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseKey failed"); + torture_assert(tctx, + ndr_policy_handle_empty(pKey), + "policy_handle non empty after CloseKey"); + + return true; +} + +static bool test_GetRootKey_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *phKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetRootKey r; + WERROR Status; + WERROR rpc_status; + + r.in.samDesired = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.Status = &Status; + r.out.rpc_status = &rpc_status; + r.out.phKey = phKey; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetRootKey_r(b, tctx, &r), + "GetRootKey failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "GetRootKey failed"); + + return true; +} + +static bool test_EnumKey_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_EnumKey r; + const char *KeyName; + NTTIME lpftLastWriteTime; + WERROR rpc_status; + + r.in.hKey = *hKey; + r.in.dwIndex = 0; + r.out.KeyName = &KeyName; + r.out.lpftLastWriteTime = &lpftLastWriteTime; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_EnumKey_r(b, tctx, &r), + "EnumKey failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "EnumKey failed"); + + return true; +} + +static bool test_OpenKey_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey, + const char *lpSubKey, + struct policy_handle *phKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenKey r; + WERROR Status; + WERROR rpc_status; + + r.in.hKey = *hKey; + r.in.lpSubKey = lpSubKey; + r.in.samDesired = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.Status = &Status; + r.out.rpc_status = &rpc_status; + r.out.phKey = phKey; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenKey_r(b, tctx, &r), + "OpenKey failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenKey failed"); + + return true; +} + +static bool test_EnumValue_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_EnumValue r; + const char *lpValueName; + uint32_t lpType; + uint32_t TotalSize; + WERROR rpc_status; + int i = 0; + + do { + uint32_t lpcbData = 2048; + + r.in.hKey = *hKey; + r.in.dwIndex = i++; + r.in.lpcbData = &lpcbData; + r.out.lpValueName = &lpValueName; + r.out.lpType = &lpType; + r.out.lpData = talloc_array(tctx, uint8_t, lpcbData); + r.out.TotalSize = &TotalSize; + r.out.rpc_status = &rpc_status; + r.out.lpcbData = &lpcbData; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_EnumValue_r(b, tctx, &r), + "EnumValue failed"); + + } while (W_ERROR_IS_OK(r.out.result)); + + torture_assert_werr_equal(tctx, + r.out.result, + WERR_NO_MORE_ITEMS, + "EnumValue failed"); + + return true; +} + +static bool test_QueryInfoKey_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_QueryInfoKey r; + uint32_t lpcSubKeys; + uint32_t lpcbMaxSubKeyLen; + uint32_t lpcValues; + uint32_t lpcbMaxValueNameLen; + uint32_t lpcbMaxValueLen; + uint32_t lpcbSecurityDescriptor; + NTTIME lpftLastWriteTime; + WERROR rpc_status; + + r.in.hKey = *hKey; + r.out.lpcSubKeys = &lpcSubKeys; + r.out.lpcbMaxSubKeyLen = &lpcbMaxSubKeyLen; + r.out.lpcValues = &lpcValues; + r.out.lpcbMaxValueNameLen = &lpcbMaxValueNameLen; + r.out.lpcbMaxValueLen = &lpcbMaxValueLen; + r.out.lpcbSecurityDescriptor = &lpcbSecurityDescriptor; + r.out.lpftLastWriteTime = &lpftLastWriteTime; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_QueryInfoKey_r(b, tctx, &r), + "QueryInfoKey failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "QueryInfoKey failed"); + + return true; +} + +static bool test_GetKeySecurity_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetKeySecurity r; + uint32_t SecurityInformation = SECINFO_DACL | SECINFO_OWNER | SECINFO_GROUP; + struct RPC_SECURITY_DESCRIPTOR pRpcSecurityDescriptor; + WERROR rpc_status; + + ZERO_STRUCT(pRpcSecurityDescriptor); + + r.in.hKey = *hKey; + r.in.SecurityInformation = SecurityInformation; + r.in.pRpcSecurityDescriptor = &pRpcSecurityDescriptor; + r.out.rpc_status = &rpc_status; + r.out.pRpcSecurityDescriptor = &pRpcSecurityDescriptor; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetKeySecurity_r(b, tctx, &r), + "GetKeySecurity failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + pRpcSecurityDescriptor.lpSecurityDescriptor = talloc_array(tctx, + uint8_t, pRpcSecurityDescriptor.cbInSecurityDescriptor); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetKeySecurity_r(b, tctx, &r), + "GetKeySecurity failed"); + } + + torture_assert_werr_ok(tctx, + r.out.result, + "GetKeySecurity failed"); + + return true; +} + +static bool test_GetRootKey(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hKey; + + if (!test_GetRootKey_int(tctx, t->p, &hKey)) { + return false; + } + + test_CloseKey_int(tctx, t->p, &hKey); + + return true; +} + +static bool test_CloseKey(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hKey; + + if (!test_GetRootKey_int(tctx, t->p, &hKey)) { + return false; + } + + return test_CloseKey_int(tctx, t->p, &hKey); +} + +static bool test_EnumKey(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hKey; + bool ret = true; + + if (!test_GetRootKey_int(tctx, t->p, &hKey)) { + return false; + } + + ret = test_EnumKey_int(tctx, t->p, &hKey); + + test_CloseKey_int(tctx, t->p, &hKey); + + return ret; +} + +static bool test_QueryValue_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey, + const char *ValueName) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_QueryValue r; + uint32_t lpValueType; + uint32_t lpcbRequired; + WERROR rpc_status; + + r.in.hKey = *hKey; + r.in.lpValueName = ValueName; + r.in.cbData = 0; + r.out.lpValueType = &lpValueType; + r.out.lpData = NULL; + r.out.lpcbRequired = &lpcbRequired; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + + r.in.cbData = lpcbRequired; + r.out.lpData = talloc_zero_array(tctx, uint8_t, r.in.cbData); + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + } + + torture_assert_werr_ok(tctx, + r.out.result, + "QueryValue failed"); + + if (lpValueType == REG_SZ) { + const char *s; + DATA_BLOB blob = data_blob_const(r.out.lpData, lpcbRequired); + pull_reg_sz(tctx, &blob, &s); + torture_comment(tctx, "got: %s\n", s); + } + + return true; +} + +static bool test_QueryValue(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hKey; + bool ret = true; + + if (!test_GetRootKey_int(tctx, t->p, &hKey)) { + return false; + } + + ret = test_QueryValue_int(tctx, t->p, &hKey, "ClusterInstanceID"); + + test_CloseKey_int(tctx, t->p, &hKey); + + return ret; +} + + +static bool test_one_key(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hKey, + const char *KeyName) +{ + struct policy_handle phKey; + + torture_assert(tctx, + test_OpenKey_int(tctx, p, hKey, KeyName, &phKey), + "failed to open key"); + + torture_assert(tctx, + test_QueryInfoKey_int(tctx, p, &phKey), + "failed to enum values"); + torture_assert(tctx, + test_GetKeySecurity_int(tctx, p, &phKey), + "failed to get key security"); + + torture_assert(tctx, + test_EnumValue_int(tctx, p, &phKey), + "failed to enum values"); + + torture_assert(tctx, + test_CloseKey_int(tctx, p, &phKey), + "failed to close key"); + + return true; +} + +static bool test_all_keys(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct policy_handle hKey; + struct clusapi_EnumKey r; + const char *KeyName; + NTTIME lpftLastWriteTime; + WERROR rpc_status; + int i = 0; + + if (!test_GetRootKey_int(tctx, t->p, &hKey)) { + return false; + } + + do { + r.in.hKey = hKey; + r.in.dwIndex = i++; + r.out.KeyName = &KeyName; + r.out.lpftLastWriteTime = &lpftLastWriteTime; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_EnumKey_r(b, tctx, &r), + "EnumKey failed"); + + if (W_ERROR_IS_OK(r.out.result)) { + torture_assert(tctx, + test_one_key(tctx, t->p, &hKey, KeyName), + "failed to test one key"); + } + + } while (W_ERROR_IS_OK(r.out.result)); + + torture_assert_werr_equal(tctx, + r.out.result, + WERR_NO_MORE_ITEMS, + "EnumKey failed"); + + test_CloseKey_int(tctx, t->p, &hKey); + + return true; +} + +static bool test_OpenGroupSet_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *lpszGroupSetName, + struct policy_handle *hGroupSet) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_OpenGroupSet r; + WERROR Status; + WERROR rpc_status; + + r.in.lpszGroupSetName = lpszGroupSetName; + r.out.rpc_status = &rpc_status; + r.out.Status = &Status; + r.out.hGroupSet = hGroupSet; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_OpenGroupSet_r(b, tctx, &r), + "OpenGroupSet failed"); + torture_assert_werr_ok(tctx, + *r.out.Status, + "OpenGroupSet failed"); + + return true; +} + +static bool test_CloseGroupSet_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *GroupSet) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_CloseGroupSet r; + + r.in.GroupSet = GroupSet; + r.out.GroupSet = GroupSet; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CloseGroupSet_r(b, tctx, &r), + "CloseGroupSet failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CloseGroupSet failed"); + torture_assert(tctx, + ndr_policy_handle_empty(GroupSet), + "policy_handle non empty after CloseGroupSet"); + + return true; +} + +static bool test_OpenGroupSet(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroupSet; + + if (t->lpwMajorVersion < 0x000a) { + torture_skip(tctx, "GroupSet fn not available on old clusters"); + return true; + } + + if (!test_OpenGroupSet_int(tctx, t->p, "Cluster Group", &hGroupSet)) { + return false; + } + + test_CloseGroupSet_int(tctx, t->p, &hGroupSet); + + return true; +} + +static bool test_CloseGroupSet(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct policy_handle hGroupSet; + + if (t->lpwMajorVersion < 0x000a) { + torture_skip(tctx, "GroupSet fn not available on old clusters"); + return true; + } + + if (!test_OpenGroupSet_int(tctx, t->p, "Cluster Group", &hGroupSet)) { + return false; + } + + return test_CloseGroupSet_int(tctx, t->p, &hGroupSet); +} + +static bool test_one_groupset(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *groupset_name) +{ + struct policy_handle hGroupSet; + + torture_assert(tctx, + test_OpenGroupSet_int(tctx, p, groupset_name, &hGroupSet), + "failed to open groupset"); + + test_CloseGroupSet_int(tctx, p, &hGroupSet); + + return true; +} + +static bool test_all_groupsets(struct torture_context *tctx, + void *data) +{ + struct torture_clusapi_context *t = + talloc_get_type_abort(data, struct torture_clusapi_context); + struct dcerpc_binding_handle *b = t->p->binding_handle; + struct clusapi_CreateGroupSetEnum r; + struct ENUM_LIST *ReturnEnum; + struct policy_handle Cluster; + WERROR rpc_status; + int i; + + if (!test_OpenCluster_int(tctx, t->p, &Cluster)) { + return false; + } + + r.in.hCluster = Cluster; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateGroupSetEnum_r(b, tctx, &r), + "CreateGroupSetEnum failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "CreateGroupSetEnum failed"); + + test_CloseCluster_int(tctx, t->p, &Cluster); + + for (i=0; i < ReturnEnum->EntryCount; i++) { + + struct ENUM_ENTRY e = ReturnEnum->Entry[i]; + + torture_assert(tctx, + test_one_groupset(tctx, t->p, e.Name), + "failed to test one groupset"); + } + + return true; +} + +static bool torture_rpc_clusapi_setup_common(struct torture_context *tctx, + struct torture_clusapi_context *t) +{ + struct dcerpc_binding_handle *b; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &t->p, &ndr_table_clusapi), + "Error connecting to server"); + + b = t->p->binding_handle; + + { + struct clusapi_GetClusterName r; + + r.out.ClusterName = &t->ClusterName; + r.out.NodeName = &t->NodeName; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetClusterName_r(b, tctx, &r), + "GetClusterName failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetClusterName failed"); + } + { + struct clusapi_GetClusterVersion2 r; + const char *lpszVendorId; + const char *lpszCSDVersion; + struct CLUSTER_OPERATIONAL_VERSION_INFO *ppClusterOpVerInfo; + WERROR rpc_status; + + r.out.lpwMajorVersion = &t->lpwMajorVersion; + r.out.lpwMinorVersion = &t->lpwMinorVersion; + r.out.lpwBuildNumber = &t->lpwBuildNumber; + r.out.lpszVendorId = &lpszVendorId; + r.out.lpszCSDVersion = &lpszCSDVersion; + r.out.ppClusterOpVerInfo = &ppClusterOpVerInfo; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetClusterVersion2_r(b, tctx, &r), + "GetClusterVersion2 failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetClusterVersion2 failed"); + } + + return true; +} + +static bool torture_rpc_clusapi_setup(struct torture_context *tctx, + void **data) +{ + struct torture_clusapi_context *t; + + *data = t = talloc_zero(tctx, struct torture_clusapi_context); + + return torture_rpc_clusapi_setup_common(tctx, t); +} + +static bool torture_rpc_clusapi_teardown(struct torture_context *tctx, + void *data) +{ + talloc_free(data); + + return true; +} + +void torture_tcase_cluster(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "OpenCluster", + test_OpenCluster); + torture_tcase_add_simple_test(tcase, "OpenClusterEx", + test_OpenClusterEx); + torture_tcase_add_simple_test(tcase, "CloseCluster", + test_CloseCluster); + torture_tcase_add_simple_test(tcase, "SetClusterName", + test_SetClusterName); + torture_tcase_add_simple_test(tcase, "GetClusterName", + test_GetClusterName); + torture_tcase_add_simple_test(tcase, "GetClusterVersion", + test_GetClusterVersion); + torture_tcase_add_simple_test(tcase, "CreateEnum", + test_CreateEnum); + torture_tcase_add_simple_test(tcase, "CreateEnumEx", + test_CreateEnumEx); + torture_tcase_add_simple_test(tcase, "GetClusterVersion2", + test_GetClusterVersion2); + torture_tcase_add_simple_test(tcase, "BackupClusterDatabase", + test_BackupClusterDatabase); + torture_tcase_add_simple_test(tcase, "SetServiceAccountPassword", + test_SetServiceAccountPassword); + torture_tcase_add_simple_test(tcase, "ClusterControl", + test_ClusterControl); + torture_tcase_add_simple_test(tcase, "CreateResTypeEnum", + test_CreateResTypeEnum); + torture_tcase_add_simple_test(tcase, "CreateGroupEnum", + test_CreateGroupEnum); + +} + +void torture_tcase_resource(struct torture_tcase *tcase) +{ + struct torture_test *test; + + torture_tcase_add_simple_test(tcase, "GetQuorumResource", + test_GetQuorumResource); + torture_tcase_add_simple_test(tcase, "SetQuorumResource", + test_SetQuorumResource); + torture_tcase_add_simple_test(tcase, "OpenResource", + test_OpenResource); + torture_tcase_add_simple_test(tcase, "OpenResourceEx", + test_OpenResourceEx); + torture_tcase_add_simple_test(tcase, "CloseResource", + test_CloseResource); + torture_tcase_add_simple_test(tcase, "CreateResource", + test_CreateResource); + torture_tcase_add_simple_test(tcase, "DeleteResource", + test_DeleteResource); + torture_tcase_add_simple_test(tcase, "SetResourceName", + test_SetResourceName); + torture_tcase_add_simple_test(tcase, "GetResourceState", + test_GetResourceState); + torture_tcase_add_simple_test(tcase, "GetResourceId", + test_GetResourceId); + torture_tcase_add_simple_test(tcase, "GetResourceType", + test_GetResourceType); + torture_tcase_add_simple_test(tcase, "CreateResEnum", + test_CreateResEnum); + test = torture_tcase_add_simple_test(tcase, "FailResource", + test_FailResource); + test->dangerous = true; + torture_tcase_add_simple_test(tcase, "OnlineResource", + test_OnlineResource); + test = torture_tcase_add_simple_test(tcase, "OfflineResource", + test_OfflineResource); + test->dangerous = true; + torture_tcase_add_simple_test(tcase, "GetResourceDependencyExpression", + test_GetResourceDependencyExpression); + torture_tcase_add_simple_test(tcase, "GetResourceNetworkName", + test_GetResourceNetworkName); + torture_tcase_add_simple_test(tcase, "all_resources", + test_all_resources); +} + +void torture_tcase_resourcetype(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "all_resourcetypes", + test_all_resourcetypes); +} + +void torture_tcase_node(struct torture_tcase *tcase) +{ + struct torture_test *test; + + torture_tcase_add_simple_test(tcase, "OpenNode", + test_OpenNode); + torture_tcase_add_simple_test(tcase, "OpenNodeEx", + test_OpenNodeEx); + torture_tcase_add_simple_test(tcase, "CloseNode", + test_CloseNode); + torture_tcase_add_simple_test(tcase, "GetNodeState", + test_GetNodeState); + torture_tcase_add_simple_test(tcase, "GetNodeId", + test_GetNodeId); + torture_tcase_add_simple_test(tcase, "NodeControl", + test_NodeControl); + test = torture_tcase_add_simple_test(tcase, "PauseNode", + test_PauseNode); + test->dangerous = true; + torture_tcase_add_simple_test(tcase, "ResumeNode", + test_ResumeNode); + test = torture_tcase_add_simple_test(tcase, "EvictNode", + test_EvictNode); + test->dangerous = true; + torture_tcase_add_simple_test(tcase, "all_nodes", + test_all_nodes); +} + +void torture_tcase_group(struct torture_tcase *tcase) +{ + struct torture_test *test; + + torture_tcase_add_simple_test(tcase, "OpenGroup", + test_OpenGroup); + torture_tcase_add_simple_test(tcase, "OpenGroupEx", + test_OpenGroupEx); + torture_tcase_add_simple_test(tcase, "CloseGroup", + test_CloseGroup); + torture_tcase_add_simple_test(tcase, "GetGroupState", + test_GetGroupState); + torture_tcase_add_simple_test(tcase, "GetGroupId", + test_GetGroupId); + torture_tcase_add_simple_test(tcase, "GroupControl", + test_GroupControl); + torture_tcase_add_simple_test(tcase, "OnlineGroup", + test_OnlineGroup); + test = torture_tcase_add_simple_test(tcase, "OfflineGroup", + test_OfflineGroup); + test->dangerous = true; + torture_tcase_add_simple_test(tcase, "all_groups", + test_all_groups); +} + +void torture_tcase_network(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "OpenNetwork", + test_OpenNetwork); + torture_tcase_add_simple_test(tcase, "OpenNetworkEx", + test_OpenNetworkEx); + torture_tcase_add_simple_test(tcase, "CloseNetwork", + test_CloseNetwork); + torture_tcase_add_simple_test(tcase, "GetNetworkState", + test_GetNetworkState); + torture_tcase_add_simple_test(tcase, "GetNetworkId", + test_GetNetworkId); + torture_tcase_add_simple_test(tcase, "all_networks", + test_all_networks); +} + +void torture_tcase_netinterface(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "OpenNetInterface", + test_OpenNetInterface); + torture_tcase_add_simple_test(tcase, "OpenNetInterfaceEx", + test_OpenNetInterfaceEx); + torture_tcase_add_simple_test(tcase, "CloseNetInterface", + test_CloseNetInterface); + torture_tcase_add_simple_test(tcase, "GetNetInterfaceState", + test_GetNetInterfaceState); + torture_tcase_add_simple_test(tcase, "GetNetInterfaceId", + test_GetNetInterfaceId); + torture_tcase_add_simple_test(tcase, "all_netinterfaces", + test_all_netinterfaces); +} + +void torture_tcase_registry(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "GetRootKey", + test_GetRootKey); + torture_tcase_add_simple_test(tcase, "CloseKey", + test_CloseKey); + torture_tcase_add_simple_test(tcase, "EnumKey", + test_EnumKey); + torture_tcase_add_simple_test(tcase, "QueryValue", + test_QueryValue); + torture_tcase_add_simple_test(tcase, "all_keys", + test_all_keys); +} + +void torture_tcase_groupset(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "OpenGroupSet", + test_OpenGroupSet); + torture_tcase_add_simple_test(tcase, "CloseGroupSet", + test_CloseGroupSet); + torture_tcase_add_simple_test(tcase, "all_groupsets", + test_all_groupsets); +} + +struct torture_suite *torture_rpc_clusapi(TALLOC_CTX *mem_ctx) +{ + struct torture_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "clusapi"); + + tcase = torture_suite_add_tcase(suite, "cluster"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_cluster(tcase); + + tcase = torture_suite_add_tcase(suite, "resource"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_resource(tcase); + + tcase = torture_suite_add_tcase(suite, "resourcetype"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_resourcetype(tcase); + + + tcase = torture_suite_add_tcase(suite, "node"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_node(tcase); + + tcase = torture_suite_add_tcase(suite, "group"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_group(tcase); + + tcase = torture_suite_add_tcase(suite, "network"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_network(tcase); + + tcase = torture_suite_add_tcase(suite, "netinterface"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_netinterface(tcase); + + tcase = torture_suite_add_tcase(suite, "registry"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_registry(tcase); + + tcase = torture_suite_add_tcase(suite, "groupset"); + + torture_tcase_set_fixture(tcase, + torture_rpc_clusapi_setup, + torture_rpc_clusapi_teardown); + + torture_tcase_groupset(tcase); + + return suite; +} diff --git a/source4/torture/rpc/countcalls.c b/source4/torture/rpc/countcalls.c new file mode 100644 index 0000000..52be979 --- /dev/null +++ b/source4/torture/rpc/countcalls.c @@ -0,0 +1,131 @@ +/* + Unix SMB/CIFS implementation. + + count number of calls on an interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@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 "librpc/ndr/libndr.h" +#include "librpc/ndr/ndr_table.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + + + +bool count_calls(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *iface, + bool all) +{ + struct dcerpc_pipe *p; + DATA_BLOB stub_in, stub_out; + int i; + NTSTATUS status = torture_rpc_connection(tctx, &p, iface); + if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND, status) + || NT_STATUS_IS_RPC(status) + || NT_STATUS_EQUAL(NT_STATUS_PORT_UNREACHABLE, status) + || NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, status)) { + if (all) { + /* Not fatal if looking for all pipes */ + return true; + } else { + printf("Failed to open '%s' to count calls - %s\n", iface->name, nt_errstr(status)); + return false; + } + } else if (!NT_STATUS_IS_OK(status)) { + printf("Failed to open '%s' to count calls - %s\n", iface->name, nt_errstr(status)); + return false; + } + + stub_in = data_blob_null; + + printf("\nScanning pipe '%s'\n", iface->name); + + for (i=0;i<500;i++) { + uint32_t out_flags = 0; + + status = dcerpc_binding_handle_raw_call(p->binding_handle, + NULL, i, + 0, /* in_flags */ + stub_in.data, + stub_in.length, + mem_ctx, + &stub_out.data, + &stub_out.length, + &out_flags); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + i--; + break; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED)) { + i--; + break; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_DISCONNECTED)) { + i--; + break; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + i--; + break; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_LOGON_FAILURE)) { + i--; + break; + } + } + + if (i==500) { + talloc_free(p); + printf("no limit on calls: %s!?\n", nt_errstr(status)); + return false; + } + + printf("Found %d calls\n", i); + + talloc_free(p); + + return true; + +} + +bool torture_rpc_countcalls(struct torture_context *torture) +{ + const struct ndr_interface_table *iface; + const char *iface_name; + bool ret = true; + const struct ndr_interface_list *l; + iface_name = lpcfg_parm_string(torture->lp_ctx, NULL, "countcalls", "interface"); + if (iface_name != NULL) { + iface = ndr_table_by_name(iface_name); + if (!iface) { + printf("Unknown interface '%s'\n", iface_name); + return false; + } + return count_calls(torture, torture, iface, false); + } + + for (l=ndr_table_list();l;l=l->next) { + TALLOC_CTX *loop_ctx; + loop_ctx = talloc_named(torture, 0, "torture_rpc_councalls loop context"); + ret &= count_calls(torture, loop_ctx, l->table, true); + talloc_free(loop_ctx); + } + return ret; +} diff --git a/source4/torture/rpc/dfs.c b/source4/torture/rpc/dfs.c new file mode 100644 index 0000000..14af288 --- /dev/null +++ b/source4/torture/rpc/dfs.c @@ -0,0 +1,651 @@ +/* + Unix SMB/CIFS implementation. + test suite for rpc dfs operations + + Copyright (C) Andrew Tridgell 2003 + + 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_dfs_c.h" +#include "libnet/libnet.h" +#include "torture/util.h" +#include "libcli/libcli.h" +#include "lib/cmdline/cmdline.h" + +#define SMBTORTURE_DFS_SHARENAME "smbtorture_dfs_share" +#define SMBTORTURE_DFS_DIRNAME "\\smbtorture_dfs_dir" +#define SMBTORTURE_DFS_PATHNAME "C:"SMBTORTURE_DFS_DIRNAME + +#define IS_DFS_VERSION_UNSUPPORTED_CALL_W2K3(x,y)\ + if (x == DFS_MANAGER_VERSION_W2K3) {\ + if (!W_ERROR_EQUAL(y,WERR_NOT_SUPPORTED)) {\ + printf("expected WERR_NOT_SUPPORTED\n");\ + return false;\ + }\ + return true;\ + }\ + +static bool test_NetShareAdd(struct torture_context *tctx, + const char *host, + const char *sharename, + const char *dir) +{ + NTSTATUS status; + struct srvsvc_NetShareInfo2 i; + struct libnet_context* libnetctx; + struct libnet_AddShare r; + + printf("Creating share %s\n", sharename); + + if (!(libnetctx = libnet_context_init(tctx->ev, tctx->lp_ctx))) { + return false; + } + + libnetctx->cred = samba_cmdline_get_creds(); + + i.name = sharename; + i.type = STYPE_DISKTREE; + i.path = dir; + i.max_users = (uint32_t) -1; + i.comment = "created by smbtorture"; + i.password = NULL; + i.permissions = 0x0; + i.current_users = 0x0; + + r.level = 2; + r.in.server_name = host; + r.in.share = i; + + status = libnet_AddShare(libnetctx, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + d_printf("Failed to add new share: %s (%s)\n", + nt_errstr(status), r.out.error_string); + return false; + } + + return true; +} + +static bool test_NetShareDel(struct torture_context *tctx, + const char *host, + const char *sharename) +{ + NTSTATUS status; + struct libnet_context* libnetctx; + struct libnet_DelShare r; + + torture_comment(tctx, "Deleting share %s\n", sharename); + + if (!(libnetctx = libnet_context_init(tctx->ev, tctx->lp_ctx))) { + return false; + } + + libnetctx->cred = samba_cmdline_get_creds(); + + r.in.share_name = sharename; + r.in.server_name = host; + + status = libnet_DelShare(libnetctx, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + d_printf("Failed to delete share: %s (%s)\n", + nt_errstr(status), r.out.error_string); + return false; + } + + return true; +} + +static bool test_CreateDir(TALLOC_CTX *mem_ctx, + struct smbcli_state **cli, + struct torture_context *tctx, + const char *host, + const char *share, + const char *dir) +{ + printf("Creating directory %s\n", dir); + + if (!torture_open_connection_share(mem_ctx, cli, tctx, host, share, tctx->ev)) { + return false; + } + + if (!torture_setup_dir(*cli, dir)) { + return false; + } + + return true; +} + +static bool test_DeleteDir(struct torture_context *tctx, + struct smbcli_state *cli, + const char *dir) +{ + torture_comment(tctx, "Deleting directory %s\n", dir); + + if (smbcli_deltree(cli->tree, dir) == -1) { + printf("Unable to delete dir %s - %s\n", dir, + smbcli_errstr(cli->tree)); + return false; + } + + return true; +} + +static bool test_GetManagerVersion_opts(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + enum dfs_ManagerVersion *version_p) +{ + struct dfs_GetManagerVersion r; + enum dfs_ManagerVersion version; + + r.out.version = &version; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_GetManagerVersion_r(b, tctx, &r), + "GetManagerVersion failed"); + + if (version_p) { + *version_p = version; + } + + return true; +} + + +static bool test_GetManagerVersion(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_GetManagerVersion_opts(tctx, b, NULL); +} + +static bool test_ManagerInitialize(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + enum dfs_ManagerVersion version; + struct dfs_ManagerInitialize r; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *host = torture_setting_string(tctx, "host", NULL); + + torture_comment(tctx, "Testing ManagerInitialize\n"); + + torture_assert(tctx, + test_GetManagerVersion_opts(tctx, b, &version), + "GetManagerVersion failed"); + + r.in.servername = host; + r.in.flags = 0; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_ManagerInitialize_r(b, tctx, &r), + "ManagerInitialize failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_warning(tctx, "dfs_ManagerInitialize failed - %s\n", + win_errstr(r.out.result)); + IS_DFS_VERSION_UNSUPPORTED_CALL_W2K3(version, r.out.result); + return false; + } + + return true; +} + +static bool test_GetInfoLevel(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint16_t level, + const char *root) +{ + struct dfs_GetInfo r; + union dfs_Info info; + + torture_comment(tctx, "Testing GetInfo level %u on '%s'\n", level, root); + + r.in.dfs_entry_path = root; + r.in.servername = NULL; + r.in.sharename = NULL; + r.in.level = level; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_GetInfo_r(b, tctx, &r), + "GetInfo failed"); + + if (!W_ERROR_IS_OK(r.out.result) && + !W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, r.out.result)) { + torture_warning(tctx, "dfs_GetInfo failed - %s\n", win_errstr(r.out.result)); + return false; + } + + return true; +} + +static bool test_GetInfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *root) +{ + bool ret = true; + /* 103, 104, 105, 106 is only available on Set */ + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 100, 101, 102, 103, 104, 105, 106}; + int i; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (!test_GetInfoLevel(tctx, b, levels[i], root)) { + ret = false; + } + } + return ret; +} + +static bool test_EnumLevelEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint16_t level, + const char *dfs_name) +{ + struct dfs_EnumEx rex; + uint32_t total=0; + struct dfs_EnumStruct e; + struct dfs_Info1 s; + struct dfs_EnumArray1 e1; + bool ret = true; + + rex.in.level = level; + rex.in.bufsize = (uint32_t)-1; + rex.in.total = &total; + rex.in.info = &e; + rex.in.dfs_name = dfs_name; + + e.level = rex.in.level; + e.e.info1 = &e1; + e.e.info1->count = 0; + e.e.info1->s = &s; + s.path = NULL; + + torture_comment(tctx, "Testing EnumEx level %u on '%s'\n", level, dfs_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_EnumEx_r(b, tctx, &rex), + "EnumEx failed"); + torture_assert_werr_ok(tctx, rex.out.result, + "EnumEx failed"); + + if (level == 1 && rex.out.total) { + int i; + for (i=0;i<*rex.out.total;i++) { + const char *root = rex.out.info->e.info1->s[i].path; + if (!test_GetInfo(tctx, b, root)) { + ret = false; + } + } + } + + if (level == 300 && rex.out.total) { + int i,k; + for (i=0;i<*rex.out.total;i++) { + uint16_t levels[] = {1, 2, 3, 4, 200}; /* 300 */ + const char *root = rex.out.info->e.info300->s[i].dom_root; + for (k=0;k<ARRAY_SIZE(levels);k++) { + if (!test_EnumLevelEx(tctx, b, + levels[k], root)) + { + ret = false; + } + } + if (!test_GetInfo(tctx, b, root)) { + ret = false; + } + } + } + + return ret; +} + + +static bool test_EnumLevel(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint16_t level) +{ + struct dfs_Enum r; + uint32_t total=0; + struct dfs_EnumStruct e; + struct dfs_Info1 s; + struct dfs_EnumArray1 e1; + bool ret = true; + + r.in.level = level; + r.in.bufsize = (uint32_t)-1; + r.in.total = &total; + r.in.info = &e; + + e.level = r.in.level; + e.e.info1 = &e1; + e.e.info1->count = 0; + e.e.info1->s = &s; + s.path = NULL; + + torture_comment(tctx, "Testing Enum level %u\n", level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_Enum_r(b, tctx, &r), + "Enum failed"); + + if (!W_ERROR_IS_OK(r.out.result) && + !W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, r.out.result)) { + torture_warning(tctx, "dfs_Enum failed - %s\n", win_errstr(r.out.result)); + return false; + } + + if (level == 1 && r.out.total) { + int i; + for (i=0;i<*r.out.total;i++) { + const char *root = r.out.info->e.info1->s[i].path; + if (!test_GetInfo(tctx, b, root)) { + ret = false; + } + } + } + + return ret; +} + + +static bool test_Enum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 200, 300}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (!test_EnumLevel(tctx, b, levels[i])) { + ret = false; + } + } + + return ret; +} + +static bool test_EnumEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 200, 300}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *host = torture_setting_string(tctx, "host", NULL); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + if (!test_EnumLevelEx(tctx, b, levels[i], host)) { + ret = false; + } + } + + return ret; +} + +static bool test_RemoveStdRoot(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host, + const char *sharename) +{ + struct dfs_RemoveStdRoot r; + + torture_comment(tctx, "Testing RemoveStdRoot\n"); + + r.in.servername = host; + r.in.rootshare = sharename; + r.in.flags = 0; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_RemoveStdRoot_r(b, tctx, &r), + "RemoveStdRoot failed"); + torture_assert_werr_ok(tctx, r.out.result, + "dfs_RemoveStdRoot failed"); + + return true; +} + +static bool test_AddStdRoot(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host, + const char *sharename) +{ + struct dfs_AddStdRoot r; + + torture_comment(tctx, "Testing AddStdRoot\n"); + + r.in.servername = host; + r.in.rootshare = sharename; + r.in.comment = "standard dfs standalone DFS root created by smbtorture (dfs_AddStdRoot)"; + r.in.flags = 0; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_AddStdRoot_r(b, tctx, &r), + "AddStdRoot failed"); + torture_assert_werr_ok(tctx, r.out.result, + "AddStdRoot failed"); + + return true; +} + +static bool test_AddStdRootForced(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host, + const char *sharename) +{ + struct dfs_AddStdRootForced r; + enum dfs_ManagerVersion version; + + torture_comment(tctx, "Testing AddStdRootForced\n"); + + torture_assert(tctx, + test_GetManagerVersion_opts(tctx, b, &version), + "GetManagerVersion failed"); + + r.in.servername = host; + r.in.rootshare = sharename; + r.in.comment = "standard dfs forced standalone DFS root created by smbtorture (dfs_AddStdRootForced)"; + r.in.store = SMBTORTURE_DFS_PATHNAME; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_AddStdRootForced_r(b, tctx, &r), + "AddStdRootForced failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_warning(tctx, "dfs_AddStdRootForced failed - %s\n", + win_errstr(r.out.result)); + IS_DFS_VERSION_UNSUPPORTED_CALL_W2K3(version, r.out.result); + return false; + } + + return test_RemoveStdRoot(tctx, b, host, sharename); +} + +static void test_cleanup_stdroot(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host, + const char *sharename, + const char *dir) +{ + struct smbcli_state *cli; + + torture_comment(tctx, "Cleaning up StdRoot\n"); + + test_RemoveStdRoot(tctx, b, host, sharename); + test_NetShareDel(tctx, host, sharename); + if (torture_open_connection_share(tctx, &cli, tctx, host, "C$", tctx->ev)) { + test_DeleteDir(tctx, cli, dir); + torture_close_connection(cli); + } +} + +static bool test_StdRoot(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const char *sharename = SMBTORTURE_DFS_SHARENAME; + const char *dir = SMBTORTURE_DFS_DIRNAME; + const char *path = SMBTORTURE_DFS_PATHNAME; + struct smbcli_state *cli; + bool ret = true; + const char *host = torture_setting_string(tctx, "host", NULL); + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing StdRoot\n"); + + test_cleanup_stdroot(tctx, b, host, sharename, dir); + + torture_assert(tctx, + test_CreateDir(tctx, &cli, tctx, host, "C$", dir), + "failed to connect C$ share and to create directory"); + torture_assert(tctx, + test_NetShareAdd(tctx, host, sharename, path), + "failed to create new share"); + + ret &= test_AddStdRoot(tctx, b, host, sharename); + ret &= test_RemoveStdRoot(tctx, b, host, sharename); + ret &= test_AddStdRootForced(tctx, b, host, sharename); + ret &= test_NetShareDel(tctx, host, sharename); + ret &= test_DeleteDir(tctx, cli, dir); + + torture_close_connection(cli); + + return ret; +} + +static bool test_GetDcAddress(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host) +{ + struct dfs_GetDcAddress r; + uint8_t is_root = 0; + uint32_t ttl = 0; + const char *ptr; + + torture_comment(tctx, "Testing GetDcAddress\n"); + + ptr = host; + + r.in.servername = host; + r.in.server_fullname = r.out.server_fullname = &ptr; + r.in.is_root = r.out.is_root = &is_root; + r.in.ttl = r.out.ttl = &ttl; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_GetDcAddress_r(b, tctx, &r), + "GetDcAddress failed"); + torture_assert_werr_ok(tctx, r.out.result, + "dfs_GetDcAddress failed"); + + return true; +} + +static bool test_SetDcAddress(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host) +{ + struct dfs_SetDcAddress r; + + torture_comment(tctx, "Testing SetDcAddress\n"); + + r.in.servername = host; + r.in.server_fullname = host; + r.in.flags = 0; + r.in.ttl = 1000; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_SetDcAddress_r(b, tctx, &r), + "SetDcAddress failed"); + torture_assert_werr_ok(tctx, r.out.result, + "dfs_SetDcAddress failed"); + + return true; +} + +static bool test_DcAddress(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const char *host = torture_setting_string(tctx, "host", NULL); + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_GetDcAddress(tctx, b, host)) { + return false; + } + + if (!test_SetDcAddress(tctx, b, host)) { + return false; + } + + return true; +} + +static bool test_FlushFtTable(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *host, + const char *sharename) +{ + struct dfs_FlushFtTable r; + enum dfs_ManagerVersion version; + + torture_comment(tctx, "Testing FlushFtTable\n"); + + torture_assert(tctx, + test_GetManagerVersion_opts(tctx, b, &version), + "GetManagerVersion failed"); + + r.in.servername = host; + r.in.rootshare = sharename; + + torture_assert_ntstatus_ok(tctx, + dcerpc_dfs_FlushFtTable_r(b, tctx, &r), + "FlushFtTable failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_warning(tctx, "dfs_FlushFtTable failed - %s\n", + win_errstr(r.out.result)); + IS_DFS_VERSION_UNSUPPORTED_CALL_W2K3(version, r.out.result); + return false; + } + + return true; +} + +static bool test_FtRoot(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const char *sharename = SMBTORTURE_DFS_SHARENAME; + const char *host = torture_setting_string(tctx, "host", NULL); + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_FlushFtTable(tctx, b, host, sharename); +} + +struct torture_suite *torture_rpc_dfs(TALLOC_CTX *mem_ctx) +{ + struct torture_rpc_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "dfs"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "netdfs", + &ndr_table_netdfs); + + torture_rpc_tcase_add_test(tcase, "GetManagerVersion", test_GetManagerVersion); + torture_rpc_tcase_add_test(tcase, "ManagerInitialize", test_ManagerInitialize); + torture_rpc_tcase_add_test(tcase, "Enum", test_Enum); + torture_rpc_tcase_add_test(tcase, "EnumEx", test_EnumEx); + torture_rpc_tcase_add_test(tcase, "StdRoot", test_StdRoot); + torture_rpc_tcase_add_test(tcase, "FtRoot", test_FtRoot); + torture_rpc_tcase_add_test(tcase, "DcAddress", test_DcAddress); + + return suite; +} diff --git a/source4/torture/rpc/drsuapi.c b/source4/torture/rpc/drsuapi.c new file mode 100644 index 0000000..d3e18ca --- /dev/null +++ b/source4/torture/rpc/drsuapi.c @@ -0,0 +1,1049 @@ +/* + Unix SMB/CIFS implementation. + + DRSUapi tests + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-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 "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/security/dom_sid.h" +#include "param/param.h" + +#define TEST_MACHINE_NAME "torturetest" + +static bool test_DsBind(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *bind_handle, + struct drsuapi_DsBindInfo28 *srv_info28) +{ + NTSTATUS status; + struct drsuapi_DsBind r; + struct GUID bind_guid; + struct drsuapi_DsBindInfo28 *bind_info28; + struct drsuapi_DsBindInfoCtr bind_info_ctr; + + ZERO_STRUCT(bind_info_ctr); + bind_info_ctr.length = 28; + + bind_info28 = &bind_info_ctr.info.info28; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7; + bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT; + + GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid); + + r.in.bind_guid = &bind_guid; + r.in.bind_info = &bind_info_ctr; + r.out.bind_handle = bind_handle; + + torture_comment(tctx, "Testing DsBind\n"); + + status = dcerpc_drsuapi_DsBind_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsBind"); + + if (srv_info28 != NULL) { + *srv_info28 = r.out.bind_info->info.info28; + } + + return true; +} + +static bool test_DsGetDomainControllerInfo(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsGetDomainControllerInfo r; + union drsuapi_DsGetDCInfoCtr ctr; + union drsuapi_DsGetDCInfoRequest req; + int32_t level_out = 0; + bool found = false; + int i, j, k; + + struct { + const char *name; + WERROR expected; + } names[] = { + { + .name = torture_join_dom_netbios_name(priv->join), + .expected = WERR_OK + }, + { + .name = torture_join_dom_dns_name(priv->join), + .expected = WERR_OK + }, + { + .name = "__UNKNOWN_DOMAIN__", + .expected = WERR_DS_OBJ_NOT_FOUND + }, + { + .name = "unknown.domain.samba.example.com", + .expected = WERR_DS_OBJ_NOT_FOUND + }, + }; + int levels[] = {1, 2}; + int level; + + for (i=0; i < ARRAY_SIZE(levels); i++) { + for (j=0; j < ARRAY_SIZE(names); j++) { + level = levels[i]; + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + + r.in.req->req1.domain_name = names[j].name; + r.in.req->req1.level = level; + + r.out.ctr = &ctr; + r.out.level_out = &level_out; + + torture_comment(tctx, + "Testing DsGetDomainControllerInfo level %d on domainname '%s'\n", + r.in.req->req1.level, r.in.req->req1.domain_name); + + status = dcerpc_drsuapi_DsGetDomainControllerInfo_r(p->binding_handle, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_drsuapi_DsGetDomainControllerInfo with dns domain failed"); + torture_assert_werr_equal(tctx, + r.out.result, names[j].expected, + "DsGetDomainControllerInfo level with dns domain failed"); + + if (!W_ERROR_IS_OK(r.out.result)) { + /* If this was an error, we can't read the result structure */ + continue; + } + + torture_assert_int_equal(tctx, + r.in.req->req1.level, *r.out.level_out, + "dcerpc_drsuapi_DsGetDomainControllerInfo in/out level differs"); + + switch (level) { + case 1: + for (k=0; k < r.out.ctr->ctr1.count; k++) { + if (strcasecmp_m(r.out.ctr->ctr1.array[k].netbios_name, + torture_join_netbios_name(priv->join)) == 0) { + found = true; + break; + } + } + break; + case 2: + for (k=0; k < r.out.ctr->ctr2.count; k++) { + if (strcasecmp_m(r.out.ctr->ctr2.array[k].netbios_name, + torture_join_netbios_name(priv->join)) == 0) { + found = true; + priv->dcinfo = r.out.ctr->ctr2.array[k]; + break; + } + } + break; + } + torture_assert(tctx, found, + "dcerpc_drsuapi_DsGetDomainControllerInfo: Failed to find the domain controller we just created during the join"); + } + } + + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + + r.out.ctr = &ctr; + r.out.level_out = &level_out; + + r.in.req->req1.domain_name = "__UNKNOWN_DOMAIN__"; /* This is clearly ignored for this level */ + r.in.req->req1.level = -1; + + torture_comment(tctx, "Testing DsGetDomainControllerInfo level %d on domainname '%s'\n", + r.in.req->req1.level, r.in.req->req1.domain_name); + + status = dcerpc_drsuapi_DsGetDomainControllerInfo_r(p->binding_handle, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_drsuapi_DsGetDomainControllerInfo with dns domain failed"); + torture_assert_werr_ok(tctx, r.out.result, + "DsGetDomainControllerInfo with dns domain failed"); + + { + const char *dc_account = talloc_asprintf(tctx, "%s\\%s$", + torture_join_dom_netbios_name(priv->join), + priv->dcinfo.netbios_name); + torture_comment(tctx, "%s: Enum active LDAP sessions searching for %s\n", __func__, dc_account); + for (k=0; k < r.out.ctr->ctr01.count; k++) { + if (strcasecmp_m(r.out.ctr->ctr01.array[k].client_account, + dc_account)) { + found = true; + break; + } + } + torture_assert(tctx, found, + "dcerpc_drsuapi_DsGetDomainControllerInfo level: Failed to find the domain controller in last logon records"); + } + + + return true; +} + +static bool test_DsWriteAccountSpn(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsWriteAccountSpn r; + union drsuapi_DsWriteAccountSpnRequest req; + struct drsuapi_DsNameString names[2]; + union drsuapi_DsWriteAccountSpnResult res; + uint32_t level_out; + + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + + torture_comment(tctx, "Testing DsWriteAccountSpn\n"); + + r.in.req->req1.operation = DRSUAPI_DS_SPN_OPERATION_ADD; + r.in.req->req1.unknown1 = 0; + r.in.req->req1.object_dn = priv->dcinfo.computer_dn; + r.in.req->req1.count = 2; + r.in.req->req1.spn_names = names; + names[0].str = talloc_asprintf(tctx, "smbtortureSPN/%s",priv->dcinfo.netbios_name); + names[1].str = talloc_asprintf(tctx, "smbtortureSPN/%s",priv->dcinfo.dns_name); + + r.out.res = &res; + r.out.level_out = &level_out; + + status = dcerpc_drsuapi_DsWriteAccountSpn_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsWriteAccountSpn"); + + r.in.req->req1.operation = DRSUAPI_DS_SPN_OPERATION_DELETE; + r.in.req->req1.unknown1 = 0; + + status = dcerpc_drsuapi_DsWriteAccountSpn_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsWriteAccountSpn"); + + return true; +} + +static bool test_DsReplicaGetInfo(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsReplicaGetInfo r; + union drsuapi_DsReplicaGetInfoRequest req; + union drsuapi_DsReplicaInfo info; + enum drsuapi_DsReplicaInfoType info_type; + int i; + struct { + int32_t level; + int32_t infotype; + const char *obj_dn; + } array[] = { + { + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_NEIGHBORS, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_CURSORS, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_KCC_DSA_CONNECT_FAILURES, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_KCC_DSA_LINK_FAILURES, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO, + DRSUAPI_DS_REPLICA_INFO_PENDING_OPS, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_CURSORS2, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_CURSORS3, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA2, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA2, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_REPSTO, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_CLIENT_CONTEXTS, + "__IGNORED__" + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_UPTODATE_VECTOR_V1, + NULL + },{ + DRSUAPI_DS_REPLICA_GET_INFO2, + DRSUAPI_DS_REPLICA_INFO_SERVER_OUTGOING_CALLS, + NULL + } + }; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping DsReplicaGetInfo test against Samba4\n"); + return true; + } + + r.in.bind_handle = &priv->bind_handle; + r.in.req = &req; + + for (i=0; i < ARRAY_SIZE(array); i++) { + const char *object_dn; + + torture_comment(tctx, "Testing DsReplicaGetInfo level %d infotype %d\n", + array[i].level, array[i].infotype); + + object_dn = (array[i].obj_dn ? array[i].obj_dn : priv->domain_obj_dn); + + r.in.level = array[i].level; + switch(r.in.level) { + case DRSUAPI_DS_REPLICA_GET_INFO: + r.in.req->req1.info_type = array[i].infotype; + r.in.req->req1.object_dn = object_dn; + ZERO_STRUCT(r.in.req->req1.source_dsa_guid); + break; + case DRSUAPI_DS_REPLICA_GET_INFO2: + r.in.req->req2.info_type = array[i].infotype; + r.in.req->req2.object_dn = object_dn; + ZERO_STRUCT(r.in.req->req2.source_dsa_guid); + r.in.req->req2.flags = 0; + r.in.req->req2.attribute_name = NULL; + r.in.req->req2.value_dn_str = NULL; + r.in.req->req2.enumeration_context = 0; + break; + } + + r.out.info = &info; + r.out.info_type = &info_type; + + status = dcerpc_drsuapi_DsReplicaGetInfo_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsReplicaGetInfo"); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) { + torture_comment(tctx, + "DsReplicaGetInfo level %d and/or infotype %d not supported by server\n", + array[i].level, array[i].infotype); + } else { + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsReplicaGetInfo"); + } + } + + return true; +} + +static bool test_DsReplicaSync(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + int i; + struct drsuapi_DsReplicaSync r; + union drsuapi_DsReplicaSyncRequest sync_req; + struct drsuapi_DsReplicaObjectIdentifier nc; + struct dom_sid null_sid; + struct { + int32_t level; + } array[] = { + { + 1 + } + }; + + if (!torture_setting_bool(tctx, "dangerous", false)) { + torture_comment(tctx, "DsReplicaSync disabled - enable dangerous tests to use\n"); + return true; + } + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping DsReplicaSync test against Samba4\n"); + return true; + } + + ZERO_STRUCT(null_sid); + + r.in.bind_handle = &priv->bind_handle; + + for (i=0; i < ARRAY_SIZE(array); i++) { + torture_comment(tctx, "Testing DsReplicaSync level %d\n", + array[i].level); + + r.in.level = array[i].level; + switch(r.in.level) { + case 1: + nc.guid = GUID_zero(); + nc.sid = null_sid; + nc.dn = priv->domain_obj_dn?priv->domain_obj_dn:""; + + sync_req.req1.naming_context = &nc; + sync_req.req1.source_dsa_guid = priv->dcinfo.ntds_guid; + sync_req.req1.source_dsa_dns = NULL; + sync_req.req1.options = 16; + + r.in.req = &sync_req; + break; + } + + status = dcerpc_drsuapi_DsReplicaSync_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsReplicaSync"); + } + + return true; +} + +static bool test_DsReplicaUpdateRefs(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsReplicaUpdateRefs r; + struct drsuapi_DsReplicaObjectIdentifier nc; + struct GUID dest_dsa_guid; + const char *dest_dsa_guid_str; + struct dom_sid null_sid; + + ZERO_STRUCT(null_sid); + dest_dsa_guid = GUID_random(); + dest_dsa_guid_str = GUID_string(tctx, &dest_dsa_guid); + + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; /* Only version 1 is defined presently */ + + /* setup NC */ + nc.guid = priv->domain_obj_dn ? GUID_zero():priv->domain_guid; + nc.sid = null_sid; + nc.dn = priv->domain_obj_dn ? priv->domain_obj_dn : ""; + + /* default setup for request */ + r.in.req.req1.naming_context = &nc; + r.in.req.req1.dest_dsa_dns_name = talloc_asprintf(tctx, "%s._msdn.%s", + dest_dsa_guid_str, + priv->domain_dns_name); + r.in.req.req1.dest_dsa_guid = dest_dsa_guid; + + /* 1. deleting replica dest should fail */ + torture_comment(tctx, "delete: %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_DEL_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_DS_DRA_REF_NOT_FOUND, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 2. hopefully adding random replica dest should succeed */ + torture_comment(tctx, "add : %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_ADD_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_OK, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 3. try adding same replica dest - should fail */ + torture_comment(tctx, "add : %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_ADD_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_DS_DRA_REF_ALREADY_EXISTS, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 4. try resetting same replica dest - should succeed */ + torture_comment(tctx, "reset : %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_DEL_REF | DRSUAPI_DRS_ADD_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_OK, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 5. delete random replicate added at step 2. */ + torture_comment(tctx, "delete : %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_DEL_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_OK, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 6. try replace on non-existing replica dest - should succeed */ + torture_comment(tctx, "replace: %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_DEL_REF | DRSUAPI_DRS_ADD_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_OK, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + /* 7. delete random replicate added at step 6. */ + torture_comment(tctx, "delete : %s\n", r.in.req.req1.dest_dsa_dns_name); + r.in.req.req1.options = DRSUAPI_DRS_DEL_REF; + status = dcerpc_drsuapi_DsReplicaUpdateRefs_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call_werr(tctx, p, + status, WERR_OK, &r, + "dcerpc_drsuapi_DsReplicaUpdateRefs"); + + return true; +} + +static bool test_DsGetNCChanges(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + int i; + struct drsuapi_DsGetNCChanges r; + union drsuapi_DsGetNCChangesRequest req; + union drsuapi_DsGetNCChangesCtr ctr; + struct drsuapi_DsReplicaObjectIdentifier nc; + struct dom_sid null_sid; + uint32_t level_out; + struct { + uint32_t level; + } array[] = { + { + 5 + }, + { + 8 + } + }; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping DsGetNCChanges test against Samba4\n"); + return true; + } + + ZERO_STRUCT(null_sid); + + for (i=0; i < ARRAY_SIZE(array); i++) { + torture_comment(tctx, + "Testing DsGetNCChanges level %d\n", + array[i].level); + + r.in.bind_handle = &priv->bind_handle; + r.in.level = array[i].level; + r.out.level_out = &level_out; + r.out.ctr = &ctr; + + switch (r.in.level) { + case 5: + nc.guid = GUID_zero(); + nc.sid = null_sid; + nc.dn = priv->domain_obj_dn ? priv->domain_obj_dn : ""; + + r.in.req = &req; + r.in.req->req5.destination_dsa_guid = GUID_random(); + r.in.req->req5.source_dsa_invocation_id = GUID_zero(); + r.in.req->req5.naming_context = &nc; + r.in.req->req5.highwatermark.tmp_highest_usn = 0; + r.in.req->req5.highwatermark.reserved_usn = 0; + r.in.req->req5.highwatermark.highest_usn = 0; + r.in.req->req5.uptodateness_vector = NULL; + r.in.req->req5.replica_flags = 0; + if (lpcfg_parm_bool(tctx->lp_ctx, NULL, "drsuapi", "compression", false)) { + r.in.req->req5.replica_flags |= DRSUAPI_DRS_USE_COMPRESSION; + } + r.in.req->req5.max_object_count = 0; + r.in.req->req5.max_ndr_size = 0; + r.in.req->req5.extended_op = DRSUAPI_EXOP_NONE; + r.in.req->req5.fsmo_info = 0; + + break; + case 8: + nc.guid = GUID_zero(); + nc.sid = null_sid; + nc.dn = priv->domain_obj_dn ? priv->domain_obj_dn : ""; + + r.in.req = &req; + r.in.req->req8.destination_dsa_guid = GUID_random(); + r.in.req->req8.source_dsa_invocation_id = GUID_zero(); + r.in.req->req8.naming_context = &nc; + r.in.req->req8.highwatermark.tmp_highest_usn = 0; + r.in.req->req8.highwatermark.reserved_usn = 0; + r.in.req->req8.highwatermark.highest_usn = 0; + r.in.req->req8.uptodateness_vector = NULL; + r.in.req->req8.replica_flags = 0; + if (lpcfg_parm_bool(tctx->lp_ctx, NULL, "drsuapi", "compression", false)) { + r.in.req->req8.replica_flags |= DRSUAPI_DRS_USE_COMPRESSION; + } + if (lpcfg_parm_bool(tctx->lp_ctx, NULL, "drsuapi", "neighbour_writeable", true)) { + r.in.req->req8.replica_flags |= DRSUAPI_DRS_WRIT_REP; + } + r.in.req->req8.replica_flags |= DRSUAPI_DRS_INIT_SYNC + | DRSUAPI_DRS_PER_SYNC + | DRSUAPI_DRS_GET_ANC + | DRSUAPI_DRS_NEVER_SYNCED + ; + r.in.req->req8.max_object_count = 402; + r.in.req->req8.max_ndr_size = 402116; + r.in.req->req8.extended_op = DRSUAPI_EXOP_NONE; + r.in.req->req8.fsmo_info = 0; + r.in.req->req8.partial_attribute_set = NULL; + r.in.req->req8.partial_attribute_set_ex = NULL; + r.in.req->req8.mapping_ctr.num_mappings = 0; + r.in.req->req8.mapping_ctr.mappings = NULL; + + break; + } + + status = dcerpc_drsuapi_DsGetNCChanges_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsGetNCChanges"); + } + + return true; +} + +bool test_QuerySitesByCost(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_QuerySitesByCost r; + union drsuapi_QuerySitesByCostRequest req; + + const char *my_site = "Default-First-Site-Name"; + const char *remote_site1 = "smbtorture-nonexisting-site1"; + const char *remote_site2 = "smbtorture-nonexisting-site2"; + + req.req1.site_from = talloc_strdup(tctx, my_site); + req.req1.num_req = 2; + req.req1.site_to = talloc_zero_array(tctx, const char *, 2); + req.req1.site_to[0] = talloc_strdup(tctx, remote_site1); + req.req1.site_to[1] = talloc_strdup(tctx, remote_site2); + req.req1.flags = 0; + + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + + status = dcerpc_drsuapi_QuerySitesByCost_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_QuerySitesByCost"); + + if (W_ERROR_IS_OK(r.out.result)) { + torture_assert_werr_equal(tctx, + r.out.ctr->ctr1.info[0].error_code, WERR_DS_OBJ_NOT_FOUND, + "dcerpc_drsuapi_QuerySitesByCost"); + torture_assert_werr_equal(tctx, + r.out.ctr->ctr1.info[1].error_code, WERR_DS_OBJ_NOT_FOUND, + "dcerpc_drsuapi_QuerySitesByCost expected error_code WERR_DS_OBJ_NOT_FOUND"); + + torture_assert_int_equal(tctx, + r.out.ctr->ctr1.info[0].site_cost, -1, + "dcerpc_drsuapi_QuerySitesByCost"); + torture_assert_int_equal(tctx, + r.out.ctr->ctr1.info[1].site_cost, -1, + "dcerpc_drsuapi_QuerySitesByCost exptected site cost"); + } + + return true; + + +} + +bool test_DsUnbind(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + struct drsuapi_DsUnbind r; + + r.in.bind_handle = &priv->bind_handle; + r.out.bind_handle = &priv->bind_handle; + + torture_comment(tctx, "Testing DsUnbind\n"); + + status = dcerpc_drsuapi_DsUnbind_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsUnbind"); + + return true; +} + + +/** + * Helper func to collect DC information for testing purposes. + * This function is almost identical to test_DsGetDomainControllerInfo + */ +bool torture_rpc_drsuapi_get_dcinfo(struct torture_context *torture, + struct DsPrivate *priv) +{ + NTSTATUS status; + int32_t level_out = 0; + struct drsuapi_DsGetDomainControllerInfo r; + union drsuapi_DsGetDCInfoCtr ctr; + int j, k; + const char *names[] = { + torture_join_dom_netbios_name(priv->join), + torture_join_dom_dns_name(priv->join)}; + + for (j=0; j < ARRAY_SIZE(names); j++) { + union drsuapi_DsGetDCInfoRequest req; + struct dcerpc_binding_handle *b = priv->drs_pipe->binding_handle; + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + + r.in.req->req1.domain_name = names[j]; + r.in.req->req1.level = 2; + + r.out.ctr = &ctr; + r.out.level_out = &level_out; + + status = dcerpc_drsuapi_DsGetDomainControllerInfo_r(b, torture, &r); + if (!NT_STATUS_IS_OK(status)) { + continue; + } + if (!W_ERROR_IS_OK(r.out.result)) { + /* If this was an error, we can't read the result structure */ + continue; + } + + for (k=0; k < r.out.ctr->ctr2.count; k++) { + if (strcasecmp_m(r.out.ctr->ctr2.array[k].netbios_name, + torture_join_netbios_name(priv->join)) == 0) { + priv->dcinfo = r.out.ctr->ctr2.array[k]; + return true; + } + } + } + + return false; +} + +/** + * Common test case setup function to be used + * in DRS suit of test when appropriate + */ +bool torture_drsuapi_tcase_setup_common(struct torture_context *tctx, struct DsPrivate *priv) +{ + NTSTATUS status; + int rnd = rand() % 1000; + char *name = talloc_asprintf(tctx, "%s%d", TEST_MACHINE_NAME, rnd); + + torture_assert(tctx, priv, "Invalid argument"); + + priv->admin_credentials = samba_cmdline_get_creds(); + + torture_comment(tctx, "Create DRSUAPI pipe\n"); + status = torture_rpc_connection(tctx, + &priv->drs_pipe, + &ndr_table_drsuapi); + torture_assert(tctx, NT_STATUS_IS_OK(status), "Unable to connect to DRSUAPI pipe"); + + torture_comment(tctx, "About to join domain with name %s\n", name); + priv->join = torture_join_domain(tctx, name, ACB_SVRTRUST, + &priv->dc_credentials); + torture_assert(tctx, priv->join, "Failed to join as BDC"); + + if (!test_DsBind(priv->drs_pipe, tctx, + &priv->bind_handle, + &priv->srv_bind_info)) + { + /* clean up */ + torture_drsuapi_tcase_teardown_common(tctx, priv); + torture_fail(tctx, "Failed execute test_DsBind()"); + } + + /* try collect some information for testing */ + torture_rpc_drsuapi_get_dcinfo(tctx, priv); + + return true; +} + +/** + * Common test case teardown function to be used + * in DRS suit of test when appropriate + */ +bool torture_drsuapi_tcase_teardown_common(struct torture_context *tctx, struct DsPrivate *priv) +{ + if (priv->join) { + torture_leave_domain(tctx, priv->join); + } + + return true; +} + +/** + * Test case setup for DRSUAPI test case + */ +static bool torture_drsuapi_tcase_setup(struct torture_context *tctx, void **data) +{ + struct DsPrivate *priv; + + *data = priv = talloc_zero(tctx, struct DsPrivate); + + return torture_drsuapi_tcase_setup_common(tctx, priv); +} + +/** + * Test case tear-down for DRSUAPI test case + */ +static bool torture_drsuapi_tcase_teardown(struct torture_context *tctx, void *data) +{ + bool ret; + struct DsPrivate *priv = talloc_get_type(data, struct DsPrivate); + + ret = torture_drsuapi_tcase_teardown_common(tctx, priv); + + talloc_free(priv); + return ret; +} + +static bool __test_DsBind_assoc_group(struct torture_context *tctx, + const char *testname, + struct DsPrivate *priv, + struct cli_credentials *creds) +{ + NTSTATUS status; + const char *err_msg; + struct drsuapi_DsCrackNames r; + union drsuapi_DsNameRequest req; + uint32_t level_out; + union drsuapi_DsNameCtr ctr; + struct drsuapi_DsNameString names[1]; + const char *dom_sid = NULL; + struct dcerpc_pipe *p1 = NULL; + struct dcerpc_pipe *p2 = NULL; + TALLOC_CTX *mem_ctx = priv; + struct dcerpc_binding *binding = NULL; + struct policy_handle ds_bind_handle = { .handle_type = 0, }; + + torture_comment(tctx, "%s: starting...\n", testname); + + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding), + "torture_rpc_binding"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, + &p1, + binding, + &ndr_table_drsuapi, + creds, + tctx->ev, + tctx->lp_ctx), + "connect p1"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, + &p2, + p1->binding, + &ndr_table_drsuapi, + creds, + tctx->ev, + tctx->lp_ctx), + "connect p2"); + + torture_assert(tctx, test_DsBind(p1, tctx, &ds_bind_handle, NULL), "DsBind"); + + ZERO_STRUCT(r); + r.in.bind_handle = &ds_bind_handle; + r.in.level = 1; + r.in.req = &req; + r.in.req->req1.codepage = 1252; /* german */ + r.in.req->req1.language = 0x00000407; /* german */ + r.in.req->req1.count = 1; + r.in.req->req1.names = names; + r.in.req->req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS; + + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY; + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + + r.out.level_out = &level_out; + r.out.ctr = &ctr; + + dom_sid = dom_sid_string(mem_ctx, torture_join_sid(priv->join)); + + names[0].str = dom_sid; + + torture_comment(tctx, "Testing DsCrackNames on p1 with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + status = dcerpc_drsuapi_DsCrackNames_r(p1->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + torture_comment(tctx, "Testing DsCrackNames on p2 with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + status = dcerpc_drsuapi_DsCrackNames_r(p2->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + TALLOC_FREE(p1); + + torture_comment(tctx, "Testing DsCrackNames on p2 (with p1 closed) with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + status = dcerpc_drsuapi_DsCrackNames_r(p2->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + torture_comment(tctx, "%s: ... finished\n", testname); + return true; +} + +static bool test_DsBindAssocGroupAdmin(struct torture_context *tctx, + struct DsPrivate *priv, + struct cli_credentials *creds) +{ + return __test_DsBind_assoc_group(tctx, __func__, priv, + priv->admin_credentials); +} + +static bool test_DsBindAssocGroupDC(struct torture_context *tctx, + struct DsPrivate *priv, + struct cli_credentials *creds) +{ + return __test_DsBind_assoc_group(tctx, __func__, priv, + priv->dc_credentials); +} + +static bool test_DsBindAssocGroupWS(struct torture_context *tctx, + struct DsPrivate *priv, + struct cli_credentials *creds) +{ + struct test_join *wks_join = NULL; + struct cli_credentials *wks_credentials = NULL; + int rnd = rand() % 1000; + char *wks_name = talloc_asprintf(tctx, "WKS%s%d", TEST_MACHINE_NAME, rnd); + bool ret; + + torture_comment(tctx, "%s: About to join workstation with name %s\n", + __func__, wks_name); + wks_join = torture_join_domain(tctx, wks_name, ACB_WSTRUST, + &wks_credentials); + torture_assert(tctx, wks_join, "Failed to join as WORKSTATION"); + ret = __test_DsBind_assoc_group(tctx, __func__, priv, + wks_credentials); + torture_leave_domain(tctx, wks_join); + return ret; +} + +/** + * DRSUAPI test case implementation + */ +void torture_rpc_drsuapi_tcase(struct torture_suite *suite) +{ + typedef bool (*run_func) (struct torture_context *test, void *tcase_data); + + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "drsuapi"); + + torture_tcase_set_fixture(tcase, torture_drsuapi_tcase_setup, + torture_drsuapi_tcase_teardown); + +#if 0 + test = torture_tcase_add_simple_test(tcase, "QuerySitesByCost", (run_func)test_QuerySitesByCost); +#endif + + torture_tcase_add_simple_test(tcase, "DsGetDomainControllerInfo", (run_func)test_DsGetDomainControllerInfo); + + torture_tcase_add_simple_test(tcase, "DsCrackNames", (run_func)test_DsCrackNames); + + torture_tcase_add_simple_test(tcase, "DsWriteAccountSpn", (run_func)test_DsWriteAccountSpn); + + torture_tcase_add_simple_test(tcase, "DsReplicaGetInfo", (run_func)test_DsReplicaGetInfo); + + torture_tcase_add_simple_test(tcase, "DsReplicaSync", (run_func)test_DsReplicaSync); + + torture_tcase_add_simple_test(tcase, "DsReplicaUpdateRefs", (run_func)test_DsReplicaUpdateRefs); + + torture_tcase_add_simple_test(tcase, "DsGetNCChanges", (run_func)test_DsGetNCChanges); + + torture_tcase_add_simple_test(tcase, "DsBindAssocGroupAdmin", (run_func)test_DsBindAssocGroupAdmin); + torture_tcase_add_simple_test(tcase, "DsBindAssocGroupDC", (run_func)test_DsBindAssocGroupDC); + torture_tcase_add_simple_test(tcase, "DsBindAssocGroupWS", (run_func)test_DsBindAssocGroupWS); +} diff --git a/source4/torture/rpc/drsuapi.h b/source4/torture/rpc/drsuapi.h new file mode 100644 index 0000000..3cc4be4 --- /dev/null +++ b/source4/torture/rpc/drsuapi.h @@ -0,0 +1,94 @@ +/* + Unix SMB/CIFS implementation. + + DRSUapi tests + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 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 "librpc/gen_ndr/drsuapi.h" + +/** + * Data structure common for most of DRSUAPI tests + */ +struct DsPrivate { + struct cli_credentials *admin_credentials; + struct dcerpc_pipe *drs_pipe; + struct policy_handle bind_handle; + struct drsuapi_DsBindInfo28 srv_bind_info; + + const char *domain_obj_dn; + const char *domain_guid_str; + const char *domain_dns_name; + struct GUID domain_guid; + struct drsuapi_DsGetDCInfo2 dcinfo; + struct test_join *join; + struct cli_credentials *dc_credentials; +}; + +/** + * Data structure of DRSUAPI W2K8 tests + * W2K8 Clients use different versions of structs + */ +struct DsPrivate_w2k8 { + struct dcerpc_pipe *drs_pipe; + struct policy_handle bind_handle; + struct GUID bind_guid; + struct drsuapi_DsBindInfoCtr srv_bind_info; + + const char *domain_obj_dn; + const char *domain_guid_str; + const char *domain_dns_name; + struct GUID domain_guid; + struct drsuapi_DsGetDCInfo3 dcinfo; + struct test_join *join; +}; + + +/** + * Custom torture macro to check dcerpc_drsuapi_ call + * return values printing more friendly messages + * \param _tctx torture context + * \param _p DCERPC pipe handle + * \param _ntstatus NTSTATUS for dcerpc_drsuapi_ call + * \param _werr_expected Expected windows error to be returned + * \param _pr in/out DCEPRC request structure - pointer + * \param _msg error message prefix + */ +#define torture_drsuapi_assert_call_werr(_tctx, _p, _ntstat, _werr_expected, _pr, _msg) \ + do { \ + NTSTATUS __nt = _ntstat; \ + if (!NT_STATUS_IS_OK(__nt)) { \ + const char *errstr = nt_errstr(__nt); \ + torture_fail(tctx, talloc_asprintf(_tctx, "%s failed - %s", _msg, errstr)); \ + } \ + torture_assert_werr_equal(_tctx, (_pr)->out.result, _werr_expected, _msg); \ + } while(0) + +/** + * Custom torture macro to check dcerpc_drsuapi_ call + * return values printing more friendly messages + * \param _tctx torture context + * \param _p DCERPC pipe handle + * \param _ntstatus NTSTATUS for dcerpc_drsuapi_ call + * \param _pr in/out DCEPRC request structure + * \param _msg error message prefix + */ +#define torture_drsuapi_assert_call(_tctx, _p, _ntstat, _pr, _msg) \ + torture_drsuapi_assert_call_werr(_tctx, _p, _ntstat, WERR_OK, _pr, _msg) + diff --git a/source4/torture/rpc/drsuapi_cracknames.c b/source4/torture/rpc/drsuapi_cracknames.c new file mode 100644 index 0000000..511309f --- /dev/null +++ b/source4/torture/rpc/drsuapi_cracknames.c @@ -0,0 +1,1087 @@ +/* + Unix SMB/CIFS implementation. + + DRSUapi tests + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 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 "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "torture/rpc/torture_rpc.h" +#include <ldb.h> +#include "libcli/security/security.h" + +#undef strcasecmp + +struct DsCrackNamesPrivate { + struct DsPrivate base; + + /* following names are used in Crack Names Matrix test */ + const char *fqdn_name; + const char *user_principal_name; + const char *service_principal_name; +}; + +static bool test_DsCrackNamesMatrix(struct torture_context *tctx, + struct DsPrivate *priv, const char *dn, + const char *user_principal_name, const char *service_principal_name) +{ + NTSTATUS status; + const char *err_msg; + struct drsuapi_DsCrackNames r; + union drsuapi_DsNameRequest req; + uint32_t level_out; + union drsuapi_DsNameCtr ctr; + struct dcerpc_pipe *p = priv->drs_pipe; + TALLOC_CTX *mem_ctx = priv; + + enum drsuapi_DsNameFormat formats[] = { + DRSUAPI_DS_NAME_FORMAT_UNKNOWN, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + DRSUAPI_DS_NAME_FORMAT_DISPLAY, + DRSUAPI_DS_NAME_FORMAT_GUID, + DRSUAPI_DS_NAME_FORMAT_CANONICAL, + DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN + }; + struct drsuapi_DsNameString names[ARRAY_SIZE(formats)]; + int i, j; + + const char *n_matrix[ARRAY_SIZE(formats)][ARRAY_SIZE(formats)]; + const char *n_from[ARRAY_SIZE(formats)]; + + ZERO_STRUCT(r); + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + r.in.req->req1.codepage = 1252; /* german */ + r.in.req->req1.language = 0x00000407; /* german */ + r.in.req->req1.count = 1; + r.in.req->req1.names = names; + r.in.req->req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS; + + r.out.level_out = &level_out; + r.out.ctr = &ctr; + + n_matrix[0][0] = dn; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + r.in.req->req1.format_desired = formats[i]; + names[0].str = dn; + torture_comment(tctx, "Testing DsCrackNames (matrix prep) with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, + "testing DsCrackNames (matrix prep) with name '%s' from format: %d desired format:%d failed - %s", + names[0].str, r.in.req->req1.format_offered, r.in.req->req1.format_desired, errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, + "testing DsCrackNames (matrix prep) with name '%s' from format: %d desired format:%d failed - %s", + names[0].str, r.in.req->req1.format_offered, r.in.req->req1.format_desired, win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } + + switch (formats[i]) { + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: + if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE) { + err_msg = talloc_asprintf(mem_ctx, + "Unexpected error (%d): This name lookup should fail", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + torture_comment(tctx, __location__ ": (expected) error\n"); + break; + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: + if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_NO_MAPPING) { + err_msg = talloc_asprintf(mem_ctx, + "Unexpected error (%d): This name lookup should fail", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + torture_comment(tctx, __location__ ": (expected) error\n"); + break; + case DRSUAPI_DS_NAME_FORMAT_UNKNOWN: /* should fail as we ask server to convert to Unknown format */ + case DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN: + if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR) { + err_msg = talloc_asprintf(mem_ctx, + "Unexpected error (%d): This name lookup should fail", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + torture_comment(tctx, __location__ ": (expected) error\n"); + break; + default: + if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames error: %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + break; + } + + switch (formats[i]) { + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: + n_from[i] = user_principal_name; + break; + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: + n_from[i] = service_principal_name; + break; + case DRSUAPI_DS_NAME_FORMAT_UNKNOWN: + case DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN: + n_from[i] = NULL; + break; + default: + n_from[i] = r.out.ctr->ctr1->array[0].result_name; + printf("%s\n", n_from[i]); + break; + } + } + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + for (j = 0; j < ARRAY_SIZE(formats); j++) { + r.in.req->req1.format_offered = formats[i]; + r.in.req->req1.format_desired = formats[j]; + if (!n_from[i]) { + n_matrix[i][j] = NULL; + continue; + } + names[0].str = n_from[i]; + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, + "testing DsCrackNames (matrix) with name '%s' from format: %d desired format:%d failed - %s", + names[0].str, r.in.req->req1.format_offered, r.in.req->req1.format_desired, errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, + "testing DsCrackNames (matrix) with name '%s' from format: %d desired format:%d failed - %s", + names[0].str, r.in.req->req1.format_offered, r.in.req->req1.format_desired, + win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } + + if (r.out.ctr->ctr1->array[0].status == DRSUAPI_DS_NAME_STATUS_OK) { + n_matrix[i][j] = r.out.ctr->ctr1->array[0].result_name; + } else { + n_matrix[i][j] = NULL; + } + } + } + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + for (j = 0; j < ARRAY_SIZE(formats); j++) { + torture_comment(tctx, "Converting %s (format %d)" + " to %d gave %s\n", + n_from[i] == NULL ? "NULL" : n_from[i], + formats[i], formats[j], + n_matrix[i][j] == NULL ? + "NULL" : n_matrix[i][j]); + + if (n_matrix[i][j] == n_from[j]) { + + /* We don't have a from name for these yet (and we can't map to them to find it out) */ + } else if (n_matrix[i][j] == NULL && n_from[i] == NULL) { + + /* we can't map to these two */ + } else if (n_matrix[i][j] == NULL && formats[j] == DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL) { + } else if (n_matrix[i][j] == NULL && formats[j] == DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL) { + } else if (n_matrix[i][j] == NULL && n_from[j] != NULL) { + err_msg = talloc_asprintf(mem_ctx, + "dcerpc_drsuapi_DsCrackNames mismatch - from %d to %d: should be %s", + formats[i], formats[j], n_from[j]); + torture_fail(tctx, err_msg); + } else if (n_matrix[i][j] != NULL && n_from[j] == NULL) { + err_msg = talloc_asprintf(mem_ctx, + "dcerpc_drsuapi_DsCrackNames mismatch - from %d to %d: should be %s", + formats[i], formats[j], n_matrix[i][j]); + torture_fail(tctx, err_msg); + } else if (strcmp(n_matrix[i][j], n_from[j]) != 0) { + err_msg = talloc_asprintf(mem_ctx, + "dcerpc_drsuapi_DsCrackNames mismatch - from %d to %d: %s should be %s", + formats[i], formats[j], n_matrix[i][j], n_from[j]); + torture_fail(tctx, err_msg); + } + } + } + + return true; +} + +bool test_DsCrackNames(struct torture_context *tctx, + struct DsPrivate *priv) +{ + NTSTATUS status; + const char *err_msg; + struct drsuapi_DsCrackNames r; + union drsuapi_DsNameRequest req; + uint32_t level_out; + union drsuapi_DsNameCtr ctr; + struct drsuapi_DsNameString names[1]; + const char *dns_domain; + const char *nt4_domain; + const char *FQDN_1779_name; + struct ldb_context *ldb; + struct ldb_dn *FQDN_1779_dn; + struct ldb_dn *realm_dn; + const char *realm_dn_str; + const char *realm_canonical; + const char *realm_canonical_ex; + const char *user_principal_name; + char *user_principal_name_short; + const char *service_principal_name; + const char *canonical_name; + const char *canonical_ex_name; + const char *dom_sid; + const char *test_dc = torture_join_netbios_name(priv->join); + struct dcerpc_pipe *p = priv->drs_pipe; + TALLOC_CTX *mem_ctx = priv; + + ZERO_STRUCT(r); + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + r.in.req->req1.codepage = 1252; /* german */ + r.in.req->req1.language = 0x00000407; /* german */ + r.in.req->req1.count = 1; + r.in.req->req1.names = names; + r.in.req->req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS; + + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY; + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + + r.out.level_out = &level_out; + r.out.ctr = &ctr; + + dom_sid = dom_sid_string(mem_ctx, torture_join_sid(priv->join)); + + names[0].str = dom_sid; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + dns_domain = r.out.ctr->ctr1->array[0].dns_domain_name; + nt4_domain = r.out.ctr->ctr1->array[0].result_name; + + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_GUID; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + priv->domain_dns_name = r.out.ctr->ctr1->array[0].dns_domain_name; + priv->domain_guid_str = r.out.ctr->ctr1->array[0].result_name; + GUID_from_string(priv->domain_guid_str, &priv->domain_guid); + + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + ldb = ldb_init(mem_ctx, tctx->ev); + + realm_dn_str = r.out.ctr->ctr1->array[0].result_name; + realm_dn = ldb_dn_new(mem_ctx, ldb, realm_dn_str); + realm_canonical = ldb_dn_canonical_string(mem_ctx, realm_dn); + + if (strcmp(realm_canonical, + talloc_asprintf(mem_ctx, "%s/", dns_domain))!= 0) { + err_msg = talloc_asprintf(mem_ctx, "local Round trip on canonical name failed: %s != %s!", + realm_canonical, + talloc_asprintf(mem_ctx, "%s/", dns_domain)); + torture_fail(tctx, err_msg); + }; + + realm_canonical_ex = ldb_dn_canonical_ex_string(mem_ctx, realm_dn); + + if (strcmp(realm_canonical_ex, + talloc_asprintf(mem_ctx, "%s\n", dns_domain))!= 0) { + err_msg = talloc_asprintf(mem_ctx, "local Round trip on canonical ex name failed: %s != %s!", + realm_canonical_ex, + talloc_asprintf(mem_ctx, "%s\n", dns_domain)); + torture_fail(tctx, err_msg); + }; + + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + names[0].str = nt4_domain; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + priv->domain_obj_dn = r.out.ctr->ctr1->array[0].result_name; + + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + names[0].str = talloc_asprintf(mem_ctx, "%s%s$", nt4_domain, test_dc); + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + FQDN_1779_name = r.out.ctr->ctr1->array[0].result_name; + + r.in.req->req1.format_offered = DRSUAPI_DS_NAME_FORMAT_GUID; + r.in.req->req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + names[0].str = priv->domain_guid_str; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed on name - %d", + r.out.ctr->ctr1->array[0].status); + torture_fail(tctx, err_msg); + } + + if (strcmp(priv->domain_dns_name, r.out.ctr->ctr1->array[0].dns_domain_name) != 0) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames failed to return same DNS name - expected %s got %s", + priv->domain_dns_name, r.out.ctr->ctr1->array[0].dns_domain_name); + torture_fail(tctx, err_msg); + } + + FQDN_1779_dn = ldb_dn_new(mem_ctx, ldb, FQDN_1779_name); + + canonical_name = ldb_dn_canonical_string(mem_ctx, FQDN_1779_dn); + canonical_ex_name = ldb_dn_canonical_ex_string(mem_ctx, FQDN_1779_dn); + + user_principal_name = talloc_asprintf(mem_ctx, "%s$@%s", test_dc, dns_domain); + + /* form up a user@DOMAIN */ + user_principal_name_short = talloc_asprintf(mem_ctx, "%s$@%s", test_dc, nt4_domain); + /* variable nt4_domain includs a trailing \ */ + user_principal_name_short[strlen(user_principal_name_short) - 1] = '\0'; + + service_principal_name = talloc_asprintf(mem_ctx, "HOST/%s", test_dc); + { + + struct { + enum drsuapi_DsNameFormat format_offered; + enum drsuapi_DsNameFormat format_desired; + const char *comment; + const char *str; + const char *expected_str; + const char *expected_dns; + enum drsuapi_DsNameStatus status; + enum drsuapi_DsNameStatus alternate_status; + enum drsuapi_DsNameFlags flags; + bool skip; + } crack[] = { + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = user_principal_name, + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = user_principal_name_short, + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = service_principal_name, + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s.%s", test_dc, dns_domain), + .comment = "ServicePrincipal Name", + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL, + .str = FQDN_1779_name, + .expected_str = canonical_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_CANONICAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = canonical_name, + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + .str = FQDN_1779_name, + .expected_str = canonical_ex_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = canonical_ex_name, + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL, + .str = FQDN_1779_name, + .comment = "DN to cannoical syntactial only", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .expected_str = canonical_name, + .flags = DRSUAPI_DS_NAME_FLAG_SYNTACTICAL_ONLY + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + .str = FQDN_1779_name, + .comment = "DN to cannoical EX syntactial only", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .expected_str = canonical_ex_name, + .flags = DRSUAPI_DS_NAME_FLAG_SYNTACTICAL_ONLY + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_DISPLAY, + .str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_GUID, + .str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = priv->domain_guid_str, + .comment = "Domain GUID to NT4 ACCOUNT", + .expected_str = nt4_domain, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL, + .str = priv->domain_guid_str, + .comment = "Domain GUID to Canonical", + .expected_str = talloc_asprintf(mem_ctx, "%s/", dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + .str = priv->domain_guid_str, + .comment = "Domain GUID to Canonical EX", + .expected_str = talloc_asprintf(mem_ctx, "%s\n", dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_DISPLAY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = "CN=Microsoft Corporation,L=Redmond,S=Washington,C=US", + .comment = "display name for Microsoft Support Account", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE, + .skip = torture_setting_bool(tctx, "samba4", false) + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = GUID_string2(mem_ctx, torture_join_user_guid(priv->join)), + .comment = "Account GUID -> DN", + .expected_str = FQDN_1779_name, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = GUID_string2(mem_ctx, torture_join_user_guid(priv->join)), + .comment = "Account GUID -> NT4 Account", + .expected_str = talloc_asprintf(mem_ctx, "%s%s$", nt4_domain, test_dc), + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = GUID_string2(mem_ctx, &priv->dcinfo.site_guid), + .comment = "Site GUID", + .expected_str = priv->dcinfo.site_dn, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = GUID_string2(mem_ctx, &priv->dcinfo.computer_guid), + .comment = "Computer GUID", + .expected_str = priv->dcinfo.computer_dn, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = GUID_string2(mem_ctx, &priv->dcinfo.computer_guid), + .comment = "Computer GUID -> NT4 Account", + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = GUID_string2(mem_ctx, &priv->dcinfo.server_guid), + .comment = "Server GUID", + .expected_str = priv->dcinfo.server_dn, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = GUID_string2(mem_ctx, &priv->dcinfo.ntds_guid), + .comment = "NTDS GUID", + .expected_str = priv->dcinfo.ntds_dn, + .status = DRSUAPI_DS_NAME_STATUS_OK, + .skip = GUID_all_zero(&priv->dcinfo.ntds_guid) + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_DISPLAY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = test_dc, + .comment = "DISPLAY NAME search for DC short name", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "krbtgt/%s", dns_domain), + .comment = "Looking for KRBTGT as a service principal", + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = dns_domain + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "bogus/%s", dns_domain), + .comment = "Looking for bogus service principal", + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = dns_domain + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "bogus/%s.%s", test_dc, dns_domain), + .comment = "Looking for bogus service on test DC", + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = talloc_asprintf(mem_ctx, "%s.%s", test_dc, dns_domain) + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "krbtgt"), + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Looking for the kadmin/changepw service as a service principal", + .str = talloc_asprintf(mem_ctx, "kadmin/changepw"), + .status = DRSUAPI_DS_NAME_STATUS_OK, + .expected_str = talloc_asprintf(mem_ctx, "CN=krbtgt,CN=Users,%s", realm_dn_str), + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s.%s@%s", + test_dc, dns_domain, + dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s.%s@%s", + test_dc, dns_domain, + "BOGUS"), + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = "BOGUS" + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s.%s@%s", + test_dc, "REALLY", + "BOGUS"), + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = "BOGUS" + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s.%s", + test_dc, dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = talloc_asprintf(mem_ctx, "cifs/%s", + test_dc), + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = "NOT A GUID", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = "NOT A SID", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = "NOT AN NT4 NAME", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .format_desired = DRSUAPI_DS_NAME_FORMAT_GUID, + .comment = "Unparsable DN", + .str = "NOT A DN", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Unparsable user principal", + .str = "NOT A PRINCIPAL", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Unparsable service principal", + .str = "NOT A SERVICE PRINCIPAL", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_GUID, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "BIND GUID (ie, not in the directory)", + .str = DRSUAPI_DS_BIND_GUID, + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Unqualified Machine account as user principal", + .str = talloc_asprintf(mem_ctx, "%s$", test_dc), + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Machine account as service principal", + .str = talloc_asprintf(mem_ctx, "%s$", test_dc), + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Full Machine account as service principal", + .str = user_principal_name, + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Realm as an NT4 domain lookup", + .str = talloc_asprintf(mem_ctx, "%s\\", dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "BUILTIN\\ -> DN", + .str = "BUILTIN\\", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "NT AUTHORITY\\ -> DN", + .str = "NT AUTHORITY\\", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "NT AUTHORITY\\ANONYMOUS LOGON -> DN", + .str = "NT AUTHORITY\\ANONYMOUS LOGON", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "NT AUTHORITY\\SYSTEM -> DN", + .str = "NT AUTHORITY\\SYSTEM", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .comment = "BUILTIN SID -> NT4 account", + .str = SID_BUILTIN, + .status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING, + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = SID_BUILTIN, + .comment = "Builtin Domain SID -> DN", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .expected_str = talloc_asprintf(mem_ctx, "CN=Builtin,%s", realm_dn_str), + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .str = SID_BUILTIN_ADMINISTRATORS, + .comment = "Builtin Administrors SID -> DN", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = SID_BUILTIN_ADMINISTRATORS, + .comment = "Builtin Administrors SID -> NT4 Account", + .status = DRSUAPI_DS_NAME_STATUS_OK, + .alternate_status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = SID_NT_ANONYMOUS, + .comment = "NT Anonymous SID -> NT4 Account", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .str = SID_NT_SYSTEM, + .comment = "NT SYSTEM SID -> NT4 Account", + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "Domain SID -> DN", + .str = dom_sid, + .expected_str = realm_dn_str, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + .format_desired = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + .comment = "Domain SID -> NT4 account", + .str = dom_sid, + .expected_str = nt4_domain, + .status = DRSUAPI_DS_NAME_STATUS_OK + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "invalid user principal name", + .str = "foo@bar", + .status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY, + .expected_dns = "bar" + }, + { + .format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + .format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + .comment = "invalid user principal name in valid domain", + .str = talloc_asprintf(mem_ctx, "invalidusername@%s", dns_domain), + .status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND + } + }; + int i; + + for (i=0; i < ARRAY_SIZE(crack); i++) { + const char *comment; + + torture_comment(tctx, "Testing DsCrackNames with name '%s'" + " offered format: %d desired format:%d\n", + crack[i].str, + crack[i].format_offered, + crack[i].format_desired); + + r.in.req->req1.format_flags = crack[i].flags; + r.in.req->req1.format_offered = crack[i].format_offered; + r.in.req->req1.format_desired = crack[i].format_desired; + names[0].str = crack[i].str; + + if (crack[i].comment) { + comment = talloc_asprintf(mem_ctx, + "'%s' with name '%s' offered format:%d desired format:%d\n", + crack[i].comment, names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + } else { + comment = talloc_asprintf(mem_ctx, "'%s' offered format:%d desired format:%d\n", + names[0].str, + r.in.req->req1.format_offered, + r.in.req->req1.format_desired); + } + if (crack[i].skip) { + torture_comment(tctx, "skipping: %s", comment); + continue; + } + status = dcerpc_drsuapi_DsCrackNames_r(p->binding_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + err_msg = talloc_asprintf(mem_ctx, "dcerpc_drsuapi_DsCrackNames failed - %s", errstr); + torture_fail(tctx, err_msg); + } else if (!W_ERROR_IS_OK(r.out.result)) { + err_msg = talloc_asprintf(mem_ctx, "DsCrackNames failed - %s", win_errstr(r.out.result)); + torture_fail(tctx, err_msg); + } else if (r.out.ctr->ctr1->array[0].status != crack[i].status) { + if (crack[i].alternate_status) { + if (r.out.ctr->ctr1->array[0].status != crack[i].alternate_status) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames unexpected status %d, wanted %d or %d on: %s", + r.out.ctr->ctr1->array[0].status, + crack[i].status, + crack[i].alternate_status, + comment); + torture_fail(tctx, err_msg); + } + } else { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames unexpected status %d, wanted %d on: %s\n", + r.out.ctr->ctr1->array[0].status, + crack[i].status, + comment); + torture_fail(tctx, err_msg); + } + } else if (crack[i].expected_str && + (!r.out.ctr->ctr1->count || + !r.out.ctr->ctr1->array[0].result_name)) + { + if (!r.out.ctr->ctr1->count) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames failed - got 0 entries, expected %s on %s", + crack[i].expected_str, comment); + torture_fail(tctx, err_msg); + } else { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames failed - got NULL pointer, expected %s on %s", + crack[i].expected_str, comment); + torture_fail(tctx, err_msg); + } + } else if (crack[i].expected_str + && (strcmp(r.out.ctr->ctr1->array[0].result_name, + crack[i].expected_str) != 0)) + { + if (strcasecmp(r.out.ctr->ctr1->array[0].result_name, + crack[i].expected_str) != 0) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames failed - got %s, expected %s on %s", + r.out.ctr->ctr1->array[0].result_name, + crack[i].expected_str, comment); + torture_fail(tctx, err_msg); + } else { + torture_comment(tctx, + "(warning) DsCrackNames returned different case - got %s, expected %s on %s\n", + r.out.ctr->ctr1->array[0].result_name, + crack[i].expected_str, comment); + } + } else if (crack[i].expected_dns + && (strcmp(r.out.ctr->ctr1->array[0].dns_domain_name, + crack[i].expected_dns) != 0)) { + err_msg = talloc_asprintf(mem_ctx, + "DsCrackNames failed - got DNS name %s, expected %s on %s", + r.out.ctr->ctr1->array[0].result_name, + crack[i].expected_str, comment); + torture_fail(tctx, err_msg); + } + + torture_comment(tctx, "Testing DsCrackNames got %s\n", r.out.ctr->ctr1->array[0].result_name); + } + } + + return test_DsCrackNamesMatrix(tctx, priv, FQDN_1779_name, + user_principal_name, service_principal_name); +} + +/** + * Test case setup for CrackNames + */ +static bool torture_drsuapi_cracknames_setup(struct torture_context *tctx, void **data) +{ + struct DsCrackNamesPrivate *priv; + + *data = priv = talloc_zero(tctx, struct DsCrackNamesPrivate); + + return torture_drsuapi_tcase_setup_common(tctx, &priv->base); +} + +/** + * Test case tear-down for CrackNames + */ +static bool torture_drsuapi_cracknames_teardown(struct torture_context *tctx, void *data) +{ + struct DsCrackNamesPrivate *priv = talloc_get_type(data, struct DsCrackNamesPrivate); + + return torture_drsuapi_tcase_teardown_common(tctx, &priv->base); +} + +/** + * CRACKNAMES test suite implementation + */ +void torture_rpc_drsuapi_cracknames_tcase(struct torture_suite *suite) +{ + typedef bool (*run_func) (struct torture_context *test, void *tcase_data); + + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "cracknames"); + + torture_tcase_set_fixture(tcase, + torture_drsuapi_cracknames_setup, + torture_drsuapi_cracknames_teardown); + + torture_tcase_add_simple_test(tcase, "cracknames-test", (run_func)test_DsCrackNames); +} diff --git a/source4/torture/rpc/drsuapi_w2k8.c b/source4/torture/rpc/drsuapi_w2k8.c new file mode 100644 index 0000000..9ff37ed --- /dev/null +++ b/source4/torture/rpc/drsuapi_w2k8.c @@ -0,0 +1,334 @@ +/* + Unix SMB/CIFS implementation. + + DRSUapi tests + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-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_c.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + +#define TEST_MACHINE_NAME "torturetest" + +/* + * DsBind as sent from W2K8 Client. + * This should work regardless of functional level, and accept + * any info <=48 + */ +bool test_DsBind_w2k8(struct torture_context *tctx, + struct DsPrivate_w2k8 *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsBind r; + struct drsuapi_DsBindInfo48 *bind_info48; + struct drsuapi_DsBindInfoCtr bind_info_ctr; + + /* We send info48 */ + ZERO_STRUCT(bind_info_ctr); + bind_info_ctr.length = 48; + + bind_info48 = &bind_info_ctr.info.info48; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7; + bind_info48->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT; + + /* + * We wish for DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2, + * needed for DsGetDomainControllerInfo level 3 + */ + bind_info48->supported_extensions_ext |= DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2; + + GUID_from_string(DRSUAPI_DS_BIND_GUID, &priv->bind_guid); + + r.in.bind_guid = &priv->bind_guid; + r.in.bind_info = &bind_info_ctr; + r.out.bind_handle = &priv->bind_handle; + + torture_comment(tctx, "Testing DsBind W2K8\n"); + + status = dcerpc_drsuapi_DsBind_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsBind"); + + torture_assert_not_null(tctx, r.out.bind_info, + "DsBind with info48 results in NULL"); + + /* cache server supported extensions, i.e. bind_info */ + priv->srv_bind_info = *r.out.bind_info; + + /* + * We do not check for length here, because it should be valid to return + * any valid info + */ + + return true; +} + +static bool test_DsGetDomainControllerInfo_w2k8(struct torture_context *tctx, + struct DsPrivate_w2k8 *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsGetDomainControllerInfo r; + union drsuapi_DsGetDCInfoCtr ctr; + int32_t level_out = 0; + uint32_t supported_extensions_ext = 0; + bool found = false; + int j, k; + + struct { + const char *name; + WERROR expected; + } names[] = { + { + .name = torture_join_dom_netbios_name(priv->join), + .expected = WERR_OK + }, + { + .name = torture_join_dom_dns_name(priv->join), + .expected = WERR_OK + }, + { + .name = "__UNKNOWN_DOMAIN__", + .expected = WERR_DS_OBJ_NOT_FOUND + }, + { + .name = "unknown.domain.samba.example.com", + .expected = WERR_DS_OBJ_NOT_FOUND + }, + }; + + /* Levels 1 and 2 are tested in standard drsuapi tests */ + int level = 3; + + /* Do Bind first. */ + if (!test_DsBind_w2k8(tctx, priv)) { + return false; + } + + /* + * We used DsBind_w2k8, so DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2 + * should mean support for level 3 + */ + + /* + * We are looking for an extension found in info32 and later + */ + switch (priv->srv_bind_info.length) { + case 32: + supported_extensions_ext = priv->srv_bind_info.info.info32.supported_extensions_ext; + break; + case 48: + supported_extensions_ext = priv->srv_bind_info.info.info48.supported_extensions_ext; + break; + default: + supported_extensions_ext = 0; + break; + } + + supported_extensions_ext &= DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2; + torture_assert(tctx, (supported_extensions_ext > 0), + "Server does not support DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2"); + + for (j=0; j < ARRAY_SIZE(names); j++) { + union drsuapi_DsGetDCInfoRequest req; + r.in.bind_handle = &priv->bind_handle; + r.in.level = 1; + r.in.req = &req; + + r.in.req->req1.domain_name = names[j].name; + r.in.req->req1.level = level; + + r.out.ctr = &ctr; + r.out.level_out = &level_out; + + torture_comment(tctx, + "Testing DsGetDomainControllerInfo level %d on domainname '%s'\n", + r.in.req->req1.level, r.in.req->req1.domain_name); + + status = dcerpc_drsuapi_DsGetDomainControllerInfo_r(p->binding_handle, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_drsuapi_DsGetDomainControllerInfo with dns domain failed"); + torture_assert_werr_equal(tctx, r.out.result, names[j].expected, + "DsGetDomainControllerInfo level with dns domain failed"); + + if (!W_ERROR_IS_OK(r.out.result)) { + /* If this was an error, we can't read the result structure */ + continue; + } + + torture_assert_int_equal(tctx, r.in.req->req1.level, *r.out.level_out, + "dcerpc_drsuapi_DsGetDomainControllerInfo in/out level differs"); + + for (k=0; k < r.out.ctr->ctr3.count; k++) { + if (strcasecmp_m(r.out.ctr->ctr3.array[k].netbios_name, + torture_join_netbios_name(priv->join)) == 0) { + found = true; + priv->dcinfo = r.out.ctr->ctr3.array[k]; + break; + } + } + break; + + torture_assert(tctx, found, + "dcerpc_drsuapi_DsGetDomainControllerInfo: Failed to find the domain controller we just created during the join"); + } + + return true; +} + + +bool test_DsUnbind_w2k8(struct torture_context *tctx, + struct DsPrivate_w2k8 *priv) +{ + NTSTATUS status; + struct dcerpc_pipe *p = priv->drs_pipe; + struct drsuapi_DsUnbind r; + + r.in.bind_handle = &priv->bind_handle; + r.out.bind_handle = &priv->bind_handle; + + torture_comment(tctx, "Testing DsUnbind W2K8\n"); + + status = dcerpc_drsuapi_DsUnbind_r(p->binding_handle, tctx, &r); + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsUnbind"); + + return true; +} + +/** + * Common test case setup function to be used + * in DRS suit of test when appropriate + */ +bool torture_drsuapi_w2k8_tcase_setup_common(struct torture_context *tctx, + struct DsPrivate_w2k8 *priv) +{ + NTSTATUS status; + int rnd = rand() % 1000; + char *name = talloc_asprintf(tctx, "%s%d", TEST_MACHINE_NAME, rnd); + struct cli_credentials *machine_credentials; + + torture_assert(tctx, priv, "Invalid argument"); + + torture_comment(tctx, "Create DRSUAPI pipe\n"); + status = torture_rpc_connection(tctx, + &priv->drs_pipe, + &ndr_table_drsuapi); + torture_assert(tctx, NT_STATUS_IS_OK(status), "Unable to connect to DRSUAPI pipe"); + + torture_comment(tctx, "About to join domain with name %s\n", name); + priv->join = torture_join_domain(tctx, name, ACB_SVRTRUST, + &machine_credentials); + torture_assert(tctx, priv->join, "Failed to join as BDC"); + + /* + * After that every test should use DsBind and DsGetDomainControllerInfo + */ + if (!test_DsBind_w2k8(tctx, priv)) { + /* clean up */ + torture_drsuapi_w2k8_tcase_teardown_common(tctx, priv); + torture_fail(tctx, "Failed execute test_DsBind_w2k8()"); + } + + + return true; +} + +/** + * Common test case teardown function to be used + * in DRS suit of test when appropriate + */ +bool torture_drsuapi_w2k8_tcase_teardown_common(struct torture_context *tctx, + struct DsPrivate_w2k8 *priv) +{ + if (priv->join) { + torture_leave_domain(tctx, priv->join); + } + + return true; +} + +/** + * Test case setup for DRSUAPI test case + */ +static bool torture_drsuapi_w2k8_tcase_setup(struct torture_context *tctx, void **data) +{ + struct DsPrivate_w2k8 *priv; + + *data = priv = talloc_zero(tctx, struct DsPrivate_w2k8); + + return torture_drsuapi_w2k8_tcase_setup_common(tctx, priv); +} + +/** + * Test case tear-down for DRSUAPI test case + */ +static bool torture_drsuapi_w2k8_tcase_teardown(struct torture_context *tctx, void *data) +{ + bool ret; + struct DsPrivate_w2k8 *priv = talloc_get_type(data, struct DsPrivate_w2k8); + + ret = torture_drsuapi_w2k8_tcase_teardown_common(tctx, priv); + + talloc_free(priv); + return ret; +} + +/** + * DRSUAPI test case implementation + */ +void torture_rpc_drsuapi_w2k8_tcase(struct torture_suite *suite) +{ + typedef bool (*run_func) (struct torture_context *test, void *tcase_data); + + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "drsuapi_w2k8"); + + torture_tcase_set_fixture(tcase, torture_drsuapi_w2k8_tcase_setup, + torture_drsuapi_w2k8_tcase_teardown); + + torture_tcase_add_simple_test(tcase, "DsBind_W2K8", (run_func)test_DsBind_w2k8); + torture_tcase_add_simple_test(tcase, "DsGetDomainControllerInfo_W2K8", (run_func)test_DsGetDomainControllerInfo_w2k8); +} diff --git a/source4/torture/rpc/dsgetinfo.c b/source4/torture/rpc/dsgetinfo.c new file mode 100644 index 0000000..b47d6ee --- /dev/null +++ b/source4/torture/rpc/dsgetinfo.c @@ -0,0 +1,452 @@ +/* + Unix SMB/CIFS implementation. + + DsGetReplInfo test. Based on code from dssync.c + + Copyright (C) Erick Nogueira do Nascimento 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 "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "libcli/cldap/cldap.h" +#include "torture/torture.h" +#include "../libcli/drsuapi/drsuapi.h" +#include "auth/gensec/gensec.h" +#include "param/param.h" +#include "dsdb/samdb/samdb.h" +#include "torture/rpc/torture_rpc.h" +#include "torture/drs/proto.h" +#include "lib/util/util_paths.h" + + +struct DsGetinfoBindInfo { + struct dcerpc_pipe *drs_pipe; + struct dcerpc_binding_handle *drs_handle; + struct drsuapi_DsBind req; + struct GUID bind_guid; + struct drsuapi_DsBindInfoCtr our_bind_info_ctr; + struct drsuapi_DsBindInfo28 our_bind_info28; + struct drsuapi_DsBindInfo28 peer_bind_info28; + struct policy_handle bind_handle; +}; + +struct DsGetinfoTest { + struct dcerpc_binding *drsuapi_binding; + + const char *ldap_url; + const char *site_name; + + const char *domain_dn; + + /* what we need to do as 'Administrator' */ + struct { + struct cli_credentials *credentials; + struct DsGetinfoBindInfo drsuapi; + } admin; +}; + + + +/* + return the default DN for a ldap server given a connected RPC pipe to the + server + */ +static const char *torture_get_ldap_base_dn(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + const char *hostname = dcerpc_binding_get_string_option(p->binding, "host"); + struct ldb_context *ldb; + const char *ldap_url = talloc_asprintf(p, "ldap://%s", hostname); + const char *attrs[] = { "defaultNamingContext", NULL }; + const char *dnstr; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + int ret; + struct ldb_result *res; + + ldb = ldb_init(tmp_ctx, tctx->ev); + if (ldb == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + + if (ldb_set_opaque(ldb, "loadparm", tctx->lp_ctx)) { + talloc_free(ldb); + return NULL; + } + + ldb_set_modules_dir(ldb, + modules_path(ldb, "ldb")); + + ret = ldb_connect(ldb, ldap_url, 0, NULL); + if (ret != LDB_SUCCESS) { + torture_comment(tctx, "Failed to make LDB connection to target"); + talloc_free(tmp_ctx); + return NULL; + } + + ret = dsdb_search_dn(ldb, tmp_ctx, &res, ldb_dn_new(tmp_ctx, ldb, ""), + attrs, 0); + if (ret != LDB_SUCCESS) { + torture_comment(tctx, "Failed to get defaultNamingContext"); + talloc_free(tmp_ctx); + return NULL; + } + + dnstr = ldb_msg_find_attr_as_string(res->msgs[0], "defaultNamingContext", NULL); + dnstr = talloc_strdup(tctx, dnstr); + talloc_free(tmp_ctx); + return dnstr; +} + + +static struct DsGetinfoTest *test_create_context(struct torture_context *tctx) +{ + NTSTATUS status; + struct DsGetinfoTest *ctx; + struct drsuapi_DsBindInfo28 *our_bind_info28; + struct drsuapi_DsBindInfoCtr *our_bind_info_ctr; + const char *binding = torture_setting_string(tctx, "binding", NULL); + ctx = talloc_zero(tctx, struct DsGetinfoTest); + if (!ctx) return NULL; + + status = dcerpc_parse_binding(ctx, binding, &ctx->drsuapi_binding); + if (!NT_STATUS_IS_OK(status)) { + printf("Bad binding string %s\n", binding); + return NULL; + } + status = dcerpc_binding_set_flags(ctx->drsuapi_binding, DCERPC_SIGN | DCERPC_SEAL, 0); + if (!NT_STATUS_IS_OK(status)) { + printf("dcerpc_binding_set_flags - %s\n", nt_errstr(status)); + return NULL; + } + + /* ctx->admin ...*/ + ctx->admin.credentials = samba_cmdline_get_creds(); + + our_bind_info28 = &ctx->admin.drsuapi.our_bind_info28; + our_bind_info28->supported_extensions = 0xFFFFFFFF; + our_bind_info28->supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + our_bind_info28->site_guid = GUID_zero(); + our_bind_info28->pid = 0; + our_bind_info28->repl_epoch = 1; + + our_bind_info_ctr = &ctx->admin.drsuapi.our_bind_info_ctr; + our_bind_info_ctr->length = 28; + our_bind_info_ctr->info.info28 = *our_bind_info28; + + GUID_from_string(DRSUAPI_DS_BIND_GUID, &ctx->admin.drsuapi.bind_guid); + + ctx->admin.drsuapi.req.in.bind_guid = &ctx->admin.drsuapi.bind_guid; + ctx->admin.drsuapi.req.in.bind_info = our_bind_info_ctr; + ctx->admin.drsuapi.req.out.bind_handle = &ctx->admin.drsuapi.bind_handle; + + return ctx; +} + +static bool _test_DsBind(struct torture_context *tctx, + struct DsGetinfoTest *ctx, struct cli_credentials *credentials, struct DsGetinfoBindInfo *b) +{ + NTSTATUS status; + bool ret = true; + + status = dcerpc_pipe_connect_b(ctx, + &b->drs_pipe, ctx->drsuapi_binding, + &ndr_table_drsuapi, + credentials, tctx->ev, tctx->lp_ctx); + + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to connect to server as a BDC: %s\n", nt_errstr(status)); + return false; + } + b->drs_handle = b->drs_pipe->binding_handle; + + status = dcerpc_drsuapi_DsBind_r(b->drs_handle, ctx, &b->req); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + printf("dcerpc_drsuapi_DsBind failed - %s\n", errstr); + ret = false; + } else if (!W_ERROR_IS_OK(b->req.out.result)) { + printf("DsBind failed - %s\n", win_errstr(b->req.out.result)); + ret = false; + } + + ZERO_STRUCT(b->peer_bind_info28); + if (b->req.out.bind_info) { + switch (b->req.out.bind_info->length) { + case 24: { + struct drsuapi_DsBindInfo24 *info24; + info24 = &b->req.out.bind_info->info.info24; + b->peer_bind_info28.supported_extensions= info24->supported_extensions; + b->peer_bind_info28.site_guid = info24->site_guid; + b->peer_bind_info28.pid = info24->pid; + b->peer_bind_info28.repl_epoch = 0; + break; + } + case 28: { + b->peer_bind_info28 = b->req.out.bind_info->info.info28; + break; + } + case 32: { + struct drsuapi_DsBindInfo32 *info32; + info32 = &b->req.out.bind_info->info.info32; + b->peer_bind_info28.supported_extensions= info32->supported_extensions; + b->peer_bind_info28.site_guid = info32->site_guid; + b->peer_bind_info28.pid = info32->pid; + b->peer_bind_info28.repl_epoch = info32->repl_epoch; + break; + } + case 48: { + struct drsuapi_DsBindInfo48 *info48; + info48 = &b->req.out.bind_info->info.info48; + b->peer_bind_info28.supported_extensions= info48->supported_extensions; + b->peer_bind_info28.site_guid = info48->site_guid; + b->peer_bind_info28.pid = info48->pid; + b->peer_bind_info28.repl_epoch = info48->repl_epoch; + break; + } + case 52: { + struct drsuapi_DsBindInfo52 *info52; + info52 = &b->req.out.bind_info->info.info52; + b->peer_bind_info28.supported_extensions= info52->supported_extensions; + b->peer_bind_info28.site_guid = info52->site_guid; + b->peer_bind_info28.pid = info52->pid; + b->peer_bind_info28.repl_epoch = info52->repl_epoch; + break; + } + default: + printf("DsBind - warning: unknown BindInfo length: %u\n", + b->req.out.bind_info->length); + } + } + + return ret; +} + + +static bool test_getinfo(struct torture_context *tctx, + struct DsGetinfoTest *ctx) +{ + NTSTATUS status; + struct dcerpc_pipe *p = ctx->admin.drsuapi.drs_pipe; + struct dcerpc_binding_handle *b = ctx->admin.drsuapi.drs_handle; + struct drsuapi_DsReplicaGetInfo r; + union drsuapi_DsReplicaGetInfoRequest req; + union drsuapi_DsReplicaInfo info; + enum drsuapi_DsReplicaInfoType info_type; + int i; + bool no_invalid_levels = true; + struct { + int32_t level; + int32_t infotype; + const char *obj_dn; + const char *attribute_name; + uint32_t flags; + } array[] = { + { + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_NEIGHBORS + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_CURSORS + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_KCC_DSA_CONNECT_FAILURES + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_KCC_DSA_LINK_FAILURES + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO, + .infotype = DRSUAPI_DS_REPLICA_INFO_PENDING_OPS + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_CURSORS2 + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_CURSORS3 + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA2, + .obj_dn = "CN=Domain Admins,CN=Users,", + .flags = 0 + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_OBJ_METADATA2, + .obj_dn = "CN=Domain Admins,CN=Users,", + .flags = DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_ATTRIBUTE_VALUE_METADATA2 + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_REPSTO + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_CLIENT_CONTEXTS + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_UPTODATE_VECTOR_V1 + },{ + .level = DRSUAPI_DS_REPLICA_GET_INFO2, + .infotype = DRSUAPI_DS_REPLICA_INFO_SERVER_OUTGOING_CALLS + } + }; + + ctx->domain_dn = torture_get_ldap_base_dn(tctx, p); + torture_assert(tctx, ctx->domain_dn != NULL, "Cannot get domain_dn"); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping DsReplicaGetInfo test against Samba4\n"); + return true; + } + + r.in.bind_handle = &ctx->admin.drsuapi.bind_handle; + r.in.req = &req; + + for (i=0; i < ARRAY_SIZE(array); i++) { + const char *object_dn; + + torture_comment(tctx, "Testing DsReplicaGetInfo level %d infotype %d\n", + array[i].level, array[i].infotype); + + if (array[i].obj_dn) { + object_dn = array[i].obj_dn; + if (object_dn[strlen(object_dn)-1] == ',') { + /* add the domain DN on the end */ + object_dn = talloc_asprintf(tctx, "%s%s", object_dn, ctx->domain_dn); + } + } else { + object_dn = ctx->domain_dn; + } + + r.in.level = array[i].level; + switch(r.in.level) { + case DRSUAPI_DS_REPLICA_GET_INFO: + r.in.req->req1.info_type = array[i].infotype; + r.in.req->req1.object_dn = object_dn; + ZERO_STRUCT(r.in.req->req1.source_dsa_guid); + break; + case DRSUAPI_DS_REPLICA_GET_INFO2: + r.in.req->req2.info_type = array[i].infotype; + r.in.req->req2.object_dn = object_dn; + ZERO_STRUCT(r.in.req->req2.source_dsa_guid); + r.in.req->req2.flags = 0; + r.in.req->req2.attribute_name = NULL; + r.in.req->req2.value_dn_str = NULL; + r.in.req->req2.enumeration_context = 0; + break; + } + + /* Construct a different request for some of the infoTypes */ + if (array[i].attribute_name != NULL) { + r.in.req->req2.attribute_name = array[i].attribute_name; + } + if (array[i].flags != 0) { + r.in.req->req2.flags |= array[i].flags; + } + + r.out.info = &info; + r.out.info_type = &info_type; + + status = dcerpc_drsuapi_DsReplicaGetInfo_r(b, tctx, &r); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) { + torture_comment(tctx, + "DsReplicaGetInfo level %d and/or infotype %d not supported by server\n", + array[i].level, array[i].infotype); + continue; + } + torture_assert_ntstatus_ok(tctx, status, talloc_asprintf(tctx, + "DsReplicaGetInfo level %d and/or infotype %d failed\n", + array[i].level, array[i].infotype)); + if (W_ERROR_EQUAL(r.out.result, WERR_INVALID_LEVEL)) { + /* this is a not yet supported level */ + torture_comment(tctx, + "DsReplicaGetInfo level %d and/or infotype %d not yet supported by server\n", + array[i].level, array[i].infotype); + no_invalid_levels = false; + continue; + } + + torture_drsuapi_assert_call(tctx, p, status, &r, "dcerpc_drsuapi_DsReplicaGetInfo"); + } + + return no_invalid_levels; +} + +/** + * DSGETINFO test case setup + */ +static bool torture_dsgetinfo_tcase_setup(struct torture_context *tctx, void **data) +{ + bool bret; + struct DsGetinfoTest *ctx; + + *data = ctx = test_create_context(tctx); + torture_assert(tctx, ctx, "test_create_context() failed"); + + bret = _test_DsBind(tctx, ctx, ctx->admin.credentials, &ctx->admin.drsuapi); + torture_assert(tctx, bret, "_test_DsBind() failed"); + + return true; +} + +/** + * DSGETINFO test case cleanup + */ +static bool torture_dsgetinfo_tcase_teardown(struct torture_context *tctx, void *data) +{ + struct DsGetinfoTest *ctx; + struct drsuapi_DsUnbind r; + struct policy_handle bind_handle; + + ctx = talloc_get_type(data, struct DsGetinfoTest); + + ZERO_STRUCT(r); + r.out.bind_handle = &bind_handle; + + /* Unbing admin handle */ + r.in.bind_handle = &ctx->admin.drsuapi.bind_handle; + if (ctx->admin.drsuapi.drs_handle) { + dcerpc_drsuapi_DsUnbind_r(ctx->admin.drsuapi.drs_handle, + ctx, &r); + } + + talloc_free(ctx); + + return true; +} + +/** + * DSGETINFO test case implementation + */ +void torture_drs_rpc_dsgetinfo_tcase(struct torture_suite *suite) +{ + typedef bool (*run_func) (struct torture_context *test, void *tcase_data); + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "dsgetinfo"); + + torture_tcase_set_fixture(tcase, + torture_dsgetinfo_tcase_setup, + torture_dsgetinfo_tcase_teardown); + + torture_tcase_add_simple_test(tcase, "DsGetReplicaInfo", (run_func)test_getinfo); +} + diff --git a/source4/torture/rpc/dssetup.c b/source4/torture/rpc/dssetup.c new file mode 100644 index 0000000..9a61199 --- /dev/null +++ b/source4/torture/rpc/dssetup.c @@ -0,0 +1,64 @@ +/* + Unix SMB/CIFS implementation. + + test suite for dssetup rpc operations + + 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 "includes.h" +#include "librpc/gen_ndr/ndr_dssetup_c.h" +#include "torture/rpc/torture_rpc.h" + + +bool test_DsRoleGetPrimaryDomainInformation_ext(struct torture_context *tctx, + struct dcerpc_pipe *p, + NTSTATUS ext_status) +{ + struct dssetup_DsRoleGetPrimaryDomainInformation r; + NTSTATUS status; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=DS_ROLE_BASIC_INFORMATION; i <= DS_ROLE_OP_STATUS; i++) { + r.in.level = i; + torture_comment(tctx, "dcerpc_dssetup_DsRoleGetPrimaryDomainInformation level %d\n", i); + + status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation_r(b, tctx, &r); + torture_assert_ntstatus_equal(tctx, ext_status, status, "DsRoleGetPrimaryDomainInformation failed"); + if (NT_STATUS_IS_OK(ext_status)) { + torture_assert_werr_ok(tctx, r.out.result, "DsRoleGetPrimaryDomainInformation failed"); + } + } + + return true; +} + +bool test_DsRoleGetPrimaryDomainInformation(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_DsRoleGetPrimaryDomainInformation_ext(tctx, p, NT_STATUS_OK); +} + +struct torture_suite *torture_rpc_dssetup(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "dssetup"); + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, "dssetup", &ndr_table_dssetup); + + torture_rpc_tcase_add_test(tcase, "DsRoleGetPrimaryDomainInformation", test_DsRoleGetPrimaryDomainInformation); + + return suite; +} diff --git a/source4/torture/rpc/echo.c b/source4/torture/rpc/echo.c new file mode 100644 index 0000000..93fd408 --- /dev/null +++ b/source4/torture/rpc/echo.c @@ -0,0 +1,474 @@ +/* + Unix SMB/CIFS implementation. + test suite for echo rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2005 + Copyright (C) Jelmer Vernooij 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 "torture/rpc/torture_rpc.h" +#include "lib/events/events.h" +#include "librpc/gen_ndr/ndr_echo_c.h" + + +/* + test the AddOne interface +*/ +#define TEST_ADDONE(tctx, value) do { \ + n = i = value; \ + r.in.in_data = n; \ + r.out.out_data = &n; \ + torture_assert_ntstatus_ok(tctx, dcerpc_echo_AddOne_r(b, tctx, &r), \ + talloc_asprintf(tctx, "AddOne(%d) failed", i)); \ + torture_assert (tctx, n == i+1, talloc_asprintf(tctx, "%d + 1 != %u (should be %u)\n", i, n, i+1)); \ + torture_comment (tctx, "%d + 1 = %u\n", i, n); \ +} while(0) + +static bool test_addone(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + uint32_t i; + uint32_t n; + struct echo_AddOne r; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0;i<10;i++) { + TEST_ADDONE(tctx, i); + } + + TEST_ADDONE(tctx, 0x7FFFFFFE); + TEST_ADDONE(tctx, 0xFFFFFFFE); + TEST_ADDONE(tctx, 0xFFFFFFFF); + TEST_ADDONE(tctx, random() & 0xFFFFFFFF); + return true; +} + +/* + test the EchoData interface +*/ +static bool test_echodata(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + int i; + uint8_t *data_in, *data_out; + int len; + struct echo_EchoData r; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "quick", false) && + (p->conn->flags & DCERPC_DEBUG_VALIDATE_BOTH)) { + len = 1 + (random() % 500); + } else { + len = 1 + (random() % 5000); + } + + data_in = talloc_array(tctx, uint8_t, len); + data_out = talloc_array(tctx, uint8_t, len); + for (i=0;i<len;i++) { + data_in[i] = i; + } + + r.in.len = len; + r.in.in_data = data_in; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_EchoData_r(b, tctx, &r), + talloc_asprintf(tctx, "EchoData(%d) failed\n", len)); + + data_out = r.out.out_data; + + for (i=0;i<len;i++) { + if (data_in[i] != data_out[i]) { + torture_comment(tctx, "Bad data returned for len %d at offset %d\n", + len, i); + torture_comment(tctx, "in:\n"); + dump_data(0, data_in+i, MIN(len-i, 16)); + torture_comment(tctx, "out:\n"); + dump_data(0, data_out+i, MIN(len-1, 16)); + return false; + } + } + return true; +} + +/* + test the SourceData interface +*/ +static bool test_sourcedata(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + int i; + int len; + struct echo_SourceData r; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "quick", false) && + (p->conn->flags & DCERPC_DEBUG_VALIDATE_BOTH)) { + len = 100 + (random() % 500); + } else { + len = 200000 + (random() % 5000); + } + + r.in.len = len; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_SourceData_r(b, tctx, &r), + talloc_asprintf(tctx, "SourceData(%d) failed", len)); + + for (i=0;i<len;i++) { + uint8_t *v = (uint8_t *)r.out.data; + torture_assert(tctx, v[i] == (i & 0xFF), + talloc_asprintf(tctx, + "bad data 0x%x at %d\n", (uint8_t)r.out.data[i], i)); + } + return true; +} + +/* + test the SinkData interface +*/ +static bool test_sinkdata(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + int i; + uint8_t *data_in; + int len; + struct echo_SinkData r; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "quick", false) && + (p->conn->flags & DCERPC_DEBUG_VALIDATE_BOTH)) { + len = 100 + (random() % 5000); + } else { + len = 200000 + (random() % 5000); + } + + data_in = talloc_array(tctx, uint8_t, len); + for (i=0;i<len;i++) { + data_in[i] = i+1; + } + + r.in.len = len; + r.in.data = data_in; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_SinkData_r(b, tctx, &r), + talloc_asprintf(tctx, "SinkData(%d) failed", len)); + + torture_comment(tctx, "sunk %d bytes\n", len); + return true; +} + + +/* + test the testcall interface +*/ +static bool test_testcall(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct echo_TestCall r; + const char *s = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.s1 = "input string"; + r.out.s2 = &s; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_TestCall_r(b, tctx, &r), + "TestCall failed"); + + torture_assert_str_equal(tctx, s, "input string", "Didn't receive back same string"); + + return true; +} + +/* + test the testcall interface +*/ +static bool test_testcall2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct echo_TestCall2 r; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=1;i<=7;i++) { + r.in.level = i; + r.out.info = talloc(tctx, union echo_Info); + + torture_comment(tctx, "Testing TestCall2 level %d\n", i); + torture_assert_ntstatus_ok(tctx, dcerpc_echo_TestCall2_r(b, tctx, &r), + "TestCall2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "TestCall2 failed"); + } + return true; +} + +static void test_sleep_done(struct tevent_req *subreq) +{ + bool *done1 = (bool *)tevent_req_callback_data_void(subreq); + *done1 = true; +} + +/* + test the TestSleep interface +*/ +static bool test_sleep(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + int i; +#define ASYNC_COUNT 3 + struct tevent_req *req[ASYNC_COUNT]; + struct echo_TestSleep r[ASYNC_COUNT]; + bool done1[ASYNC_COUNT]; + bool done2[ASYNC_COUNT]; + struct timeval snd[ASYNC_COUNT]; + struct timeval rcv[ASYNC_COUNT]; + struct timeval diff[ASYNC_COUNT]; + int total_done = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport; + uint32_t assoc_group_id; + struct dcerpc_pipe *p2 = NULL; + NTSTATUS status; + + if (torture_setting_bool(tctx, "quick", false)) { + torture_skip(tctx, "TestSleep disabled - use \"torture:quick=no\" to enable\n"); + } + torture_comment(tctx, "Testing TestSleep - use \"torture:quick=yes\" to disable\n"); + + transport = dcerpc_binding_get_transport(p->binding); + assoc_group_id = dcerpc_binding_get_assoc_group_id(p->binding); + + torture_comment(tctx, "connect echo connection 2 with " + "DCERPC_CONCURRENT_MULTIPLEX\n"); + status = torture_rpc_connection_transport(tctx, &p2, + &ndr_table_rpcecho, + transport, + assoc_group_id, + DCERPC_CONCURRENT_MULTIPLEX); + torture_assert_ntstatus_ok(tctx, status, "opening echo connection 2"); + b = p2->binding_handle; + + for (i=0;i<ASYNC_COUNT;i++) { + done1[i] = false; + done2[i] = false; + snd[i] = timeval_current(); + rcv[i] = timeval_zero(); + r[i].in.seconds = ASYNC_COUNT-i; + req[i] = dcerpc_echo_TestSleep_r_send(tctx, tctx->ev, b, &r[i]); + torture_assert(tctx, req[i], "Failed to send async sleep request\n"); + tevent_req_set_callback(req[i], test_sleep_done, &done1[i]); + } + + while (total_done < ASYNC_COUNT) { + torture_assert(tctx, tevent_loop_once(tctx->ev) == 0, + "Event context loop failed"); + for (i=0;i<ASYNC_COUNT;i++) { + if (done2[i] == false && done1[i] == true) { + int rounded_tdiff; + total_done++; + done2[i] = true; + rcv[i] = timeval_current(); + diff[i] = timeval_until(&snd[i], &rcv[i]); + rounded_tdiff = (int)(0.5 + diff[i].tv_sec + (1.0e-6*diff[i].tv_usec)); + torture_comment(tctx, "rounded_tdiff=%d\n", rounded_tdiff); + torture_assert_ntstatus_ok(tctx, + dcerpc_echo_TestSleep_r_recv(req[i], tctx), + talloc_asprintf(tctx, "TestSleep(%d) failed", i)); + torture_assert(tctx, r[i].out.result == r[i].in.seconds, + talloc_asprintf(tctx, "Failed - Asked to sleep for %u seconds (server replied with %u seconds and the reply takes only %u seconds)", + r[i].out.result, r[i].in.seconds, (unsigned int)diff[i].tv_sec)); + torture_assert(tctx, r[i].out.result <= rounded_tdiff, + talloc_asprintf(tctx, "Failed - Slept for %u seconds (but reply takes only %u.%06u seconds)", + r[i].out.result, (unsigned int)diff[i].tv_sec, (unsigned int)diff[i].tv_usec)); + if (r[i].out.result+1 == rounded_tdiff) { + torture_comment(tctx, "Slept for %u seconds (but reply takes %u.%06u seconds - busy server?)\n", + r[i].out.result, (unsigned int)diff[i].tv_sec, (unsigned int)diff[i].tv_usec); + } else if (r[i].out.result == rounded_tdiff) { + torture_comment(tctx, "Slept for %u seconds (reply takes %u.%06u seconds - ok)\n", + r[i].out.result, (unsigned int)diff[i].tv_sec, (unsigned int)diff[i].tv_usec); + } else { + torture_fail(tctx, talloc_asprintf(tctx, + "(Failed) - Not async - Slept for %u seconds (but reply takes %u.%06u seconds)\n", + r[i].out.result, (unsigned int)diff[i].tv_sec, (unsigned int)diff[i].tv_usec)); + } + } + } + } + torture_comment(tctx, "\n"); + return true; +} + +/* + test enum handling +*/ +static bool test_enum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct echo_TestEnum r; + enum echo_Enum1 v = ECHO_ENUM1; + struct echo_Enum2 e2; + union echo_Enum3 e3; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.foo1 = &v; + r.in.foo2 = &e2; + r.in.foo3 = &e3; + r.out.foo1 = &v; + r.out.foo2 = &e2; + r.out.foo3 = &e3; + + e2.e1 = 76; + e2.e2 = ECHO_ENUM1_32; + e3.e1 = ECHO_ENUM2; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_TestEnum_r(b, tctx, &r), + "TestEnum failed"); + return true; +} + +/* + test surrounding conformant array handling +*/ +static bool test_surrounding(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct echo_TestSurrounding r; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + r.in.data = talloc(tctx, struct echo_Surrounding); + + r.in.data->x = 20; + r.in.data->surrounding = talloc_zero_array(tctx, uint16_t, r.in.data->x); + + r.out.data = talloc(tctx, struct echo_Surrounding); + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_TestSurrounding_r(b, tctx, &r), + "TestSurrounding failed"); + + torture_assert(tctx, r.out.data->x == 2 * r.in.data->x, + "TestSurrounding did not make the array twice as large"); + + return true; +} + +/* + test multiple levels of pointers +*/ +static bool test_doublepointer(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct echo_TestDoublePointer r; + uint16_t value = 12; + uint16_t *pvalue = &value; + uint16_t **ppvalue = &pvalue; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + r.in.data = &ppvalue; + + torture_assert_ntstatus_ok(tctx, dcerpc_echo_TestDoublePointer_r(b, tctx, &r), + "TestDoublePointer failed"); + + torture_assert_int_equal(tctx, value, r.out.result, + "TestDoublePointer did not return original value"); + return true; +} + + +/* + test request timeouts +*/ +#if 0 /* this test needs fixing to work over ncacn_np */ +static bool test_timeout(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct rpc_request *req; + struct echo_TestSleep r; + int timeout_saved = p->request_timeout; + + if (torture_setting_bool(tctx, "quick", false)) { + torture_skip(tctx, "timeout testing disabled - use \"torture:quick=no\" to enable\n"); + } + + torture_comment(tctx, "testing request timeouts\n"); + r.in.seconds = 2; + p->request_timeout = 1; + + req = dcerpc_echo_TestSleep_send(p, tctx, &r); + if (!req) { + torture_comment(tctx, "Failed to send async sleep request\n"); + goto failed; + } + req->ignore_timeout = true; + + status = dcerpc_echo_TestSleep_recv(req); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_IO_TIMEOUT, + "request should have timed out"); + + torture_comment(tctx, "testing request destruction\n"); + req = dcerpc_echo_TestSleep_send(p, tctx, &r); + if (!req) { + torture_comment(tctx, "Failed to send async sleep request\n"); + goto failed; + } + talloc_free(req); + + req = dcerpc_echo_TestSleep_send(p, tctx, &r); + if (!req) { + torture_comment(tctx, "Failed to send async sleep request\n"); + goto failed; + } + req->ignore_timeout = true; + status = dcerpc_echo_TestSleep_recv(req); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_IO_TIMEOUT, + "request should have timed out"); + + p->request_timeout = timeout_saved; + + return test_addone(tctx, p); + +failed: + p->request_timeout = timeout_saved; + return false; +} +#endif + +struct torture_suite *torture_rpc_echo(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "echo"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "echo", + &ndr_table_rpcecho); + + torture_rpc_tcase_add_test(tcase, "addone", test_addone); + torture_rpc_tcase_add_test(tcase, "sinkdata", test_sinkdata); + torture_rpc_tcase_add_test(tcase, "echodata", test_echodata); + torture_rpc_tcase_add_test(tcase, "sourcedata", test_sourcedata); + torture_rpc_tcase_add_test(tcase, "testcall", test_testcall); + torture_rpc_tcase_add_test(tcase, "testcall2", test_testcall2); + torture_rpc_tcase_add_test(tcase, "enum", test_enum); + torture_rpc_tcase_add_test(tcase, "surrounding", test_surrounding); + torture_rpc_tcase_add_test(tcase, "doublepointer", test_doublepointer); + torture_rpc_tcase_add_test(tcase, "sleep", test_sleep); +#if 0 /* this test needs fixing to work over ncacn_np */ + torture_rpc_tcase_add_test(tcase, "timeout", test_timeout); +#endif + + return suite; +} diff --git a/source4/torture/rpc/epmapper.c b/source4/torture/rpc/epmapper.c new file mode 100644 index 0000000..72b5165 --- /dev/null +++ b/source4/torture/rpc/epmapper.c @@ -0,0 +1,679 @@ +/* + Unix SMB/CIFS implementation. + test suite for epmapper rpc operations + + Copyright (C) Andrew Tridgell 2003 + + 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_c.h" +#include "librpc/ndr/ndr_table.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "torture/rpc/torture_rpc.h" +#include "lib/util/util_net.h" +#include "librpc/rpc/rpc_common.h" + +/* + display any protocol tower + */ +static void display_tower(struct torture_context *tctx, struct epm_tower *twr) +{ + int i; + + for (i = 0; i < twr->num_floors; i++) { + torture_comment(tctx, + " %s", + epm_floor_string(tctx, &twr->floors[i])); + } + torture_comment(tctx, "\n"); +} + +static bool test_Insert(struct torture_context *tctx, + struct dcerpc_binding_handle *h, + struct ndr_syntax_id object, + const char *annotation, + const struct dcerpc_binding *b) +{ + struct epm_Insert r; + NTSTATUS status; + + r.in.num_ents = 1; + r.in.entries = talloc_array(tctx, struct epm_entry_t, 1); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "Skip Insert test against Samba4"); + } + + /* FIXME zero */ + ZERO_STRUCT(r.in.entries[0].object); + r.in.entries[0].annotation = annotation; + + r.in.entries[0].tower = talloc(tctx, struct epm_twr_t); + + status = dcerpc_binding_build_tower(tctx, + b, + &r.in.entries[0].tower->tower); + + torture_assert_ntstatus_ok(tctx, + status, + "Unable to build tower from binding struct"); + r.in.replace = 0; + + /* shoot! */ + status = dcerpc_epm_Insert_r(h, tctx, &r); + + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, + "epm_Insert failed - %s\n", + nt_errstr(status)); + return false; + } + + if (r.out.result != EPMAPPER_STATUS_OK) { + torture_comment(tctx, + "epm_Insert failed - internal error: 0x%.4x\n", + r.out.result); + return false; + } + + return true; +} + +static bool test_Delete(struct torture_context *tctx, + struct dcerpc_binding_handle *h, + const char *annotation, + const struct dcerpc_binding *b) +{ + NTSTATUS status; + struct epm_Delete r; + + r.in.num_ents = 1; + r.in.entries = talloc_array(tctx, struct epm_entry_t, 1); + + ZERO_STRUCT(r.in.entries[0].object); + r.in.entries[0].annotation = annotation; + + r.in.entries[0].tower = talloc(tctx, struct epm_twr_t); + + status = dcerpc_binding_build_tower(tctx, + b, + &r.in.entries[0].tower->tower); + + torture_assert_ntstatus_ok(tctx, + status, + "Unable to build tower from binding struct"); + r.in.num_ents = 1; + + status = dcerpc_epm_Delete_r(h, tctx, &r); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, + "epm_Delete failed - %s\n", + nt_errstr(status)); + return false; + } + + if (r.out.result != EPMAPPER_STATUS_OK) { + torture_comment(tctx, + "epm_Delete failed - internal error: 0x%.4x\n", + r.out.result); + return false; + } + + return true; +} + +static bool test_Map_tcpip(struct torture_context *tctx, + struct dcerpc_binding_handle *h, + struct ndr_syntax_id map_syntax) +{ + struct epm_Map r; + struct GUID uuid; + struct policy_handle entry_handle; + struct ndr_syntax_id syntax; + struct dcerpc_binding *map_binding; + struct epm_twr_t map_tower; + struct epm_twr_p_t towers[20]; + struct epm_tower t; + uint32_t num_towers; + uint32_t port; + uint32_t i; + long int p; + const char *tmp; + const char *ip; + char *ptr; + NTSTATUS status; + + torture_comment(tctx, "Testing epm_Map\n"); + + ZERO_STRUCT(uuid); + ZERO_STRUCT(entry_handle); + + r.in.object = &uuid; + r.in.map_tower = &map_tower; + r.in.entry_handle = &entry_handle; + r.out.entry_handle = &entry_handle; + r.in.max_towers = 10; + r.out.towers = towers; + r.out.num_towers = &num_towers; + + /* Create map tower */ + status = dcerpc_parse_binding(tctx, "ncacn_ip_tcp:[135]", &map_binding); + torture_assert_ntstatus_ok(tctx, status, + "epm_Map_tcpip failed: can't create map_binding"); + + status = dcerpc_binding_set_abstract_syntax(map_binding, &map_syntax); + torture_assert_ntstatus_ok(tctx, status, + "epm_Map_tcpip failed: set map_syntax"); + + status = dcerpc_binding_build_tower(tctx, map_binding, + &map_tower.tower); + torture_assert_ntstatus_ok(tctx, status, + "epm_Map_tcpip failed: can't create map_tower"); + + torture_comment(tctx, + "epm_Map request for '%s':\n", + ndr_interface_name(&map_syntax.uuid, map_syntax.if_version)); + display_tower(tctx, &r.in.map_tower->tower); + + status = dcerpc_epm_Map_r(h, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "epm_Map_simple failed"); + torture_assert(tctx, r.out.result == EPMAPPER_STATUS_OK, + "epm_Map_tcpip failed: result is not EPMAPPER_STATUS_OK"); + + /* Check the result */ + t = r.out.towers[0].twr->tower; + + /* Check if we got the correct RPC interface identifier */ + dcerpc_floor_get_lhs_data(&t.floors[0], &syntax); + torture_assert(tctx, ndr_syntax_id_equal(&syntax, &map_syntax), + "epm_Map_tcpip failed: Interface identifier mismatch"); + + torture_comment(tctx, + "epm_Map_tcpip response for '%s':\n", + ndr_interface_name(&syntax.uuid, syntax.if_version)); + + dcerpc_floor_get_lhs_data(&t.floors[1], &syntax); + torture_assert(tctx, ndr_syntax_id_equal(&syntax, &ndr_transfer_syntax_ndr), + "epm_Map_tcpip failed: floor 2 is not NDR encoded"); + + torture_assert(tctx, t.floors[2].lhs.protocol == EPM_PROTOCOL_NCACN, + "epm_Map_tcpip failed: floor 3 is not NCACN_IP_TCP"); + + tmp = dcerpc_floor_get_rhs_data(tctx, &t.floors[3]); + p = strtol(tmp, &ptr, 10); + port = p & 0xffff; + torture_assert(tctx, port > 1024 && port < 65535, "epm_Map_tcpip failed"); + + ip = dcerpc_floor_get_rhs_data(tctx, &t.floors[4]); + torture_assert(tctx, is_ipaddress(ip), "epm_Map_tcpip failed"); + + for (i = 0; i < *r.out.num_towers; i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &t); + } + } + + return true; +} + +static bool test_Map_full(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const struct ndr_syntax_id obj = { + { 0x8a885d04, 0x1ceb, 0x11c9, {0x9f, 0xe8}, {0x08,0x00,0x2b,0x10,0x48,0x60} }, + 2 + }; + struct dcerpc_binding_handle *h = p->binding_handle; + const char *annotation = "SMBTORTURE"; + struct dcerpc_binding *b; + NTSTATUS status; + bool ok; + + status = dcerpc_parse_binding(tctx, "ncacn_ip_tcp:216.83.154.106[41768]", &b); + torture_assert_ntstatus_ok(tctx, + status, + "Unable to generate dcerpc_binding struct"); + status = dcerpc_binding_set_abstract_syntax(b, &obj); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_binding_set_abstract_syntax"); + + ok = test_Insert(tctx, h, obj, annotation, b); + torture_assert(tctx, ok, "test_Insert failed"); + + ok = test_Map_tcpip(tctx, h, obj); + torture_assert(tctx, ok, "test_Map_tcpip failed"); + + ok = test_Delete(tctx, h, annotation, b); + torture_assert(tctx, ok, "test_Delete failed"); + + return true; +} + +static bool test_Map_display(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct epm_entry_t *entry) + +{ + NTSTATUS status; + struct epm_twr_t *twr = entry->tower; + struct epm_Map r; + struct GUID uuid = entry->object; + struct policy_handle handle; + struct ndr_syntax_id syntax; + uint32_t num_towers; + uint32_t i; + + ZERO_STRUCT(handle); + + r.in.object = &uuid; + r.in.map_tower = twr; + r.in.entry_handle = &handle; + r.out.entry_handle = &handle; + r.in.max_towers = 10; + r.out.num_towers = &num_towers; + + dcerpc_floor_get_lhs_data(&twr->tower.floors[0], &syntax); + + torture_comment(tctx, + "epm_Map results for '%s':\n", + ndr_interface_name(&syntax.uuid, syntax.if_version)); + + status = dcerpc_epm_Map_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && r.out.result == 0) { + for (i=0;i<*r.out.num_towers;i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &r.out.towers[i].twr->tower); + } + } + } + + /* RPC protocol identifier */ + twr->tower.floors[2].lhs.protocol = EPM_PROTOCOL_NCACN; + twr->tower.floors[2].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[2].rhs.ncacn.minor_version = 0; + + /* Port address */ + twr->tower.floors[3].lhs.protocol = EPM_PROTOCOL_TCP; + twr->tower.floors[3].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[3].rhs.tcp.port = 0; + + /* Transport */ + twr->tower.floors[4].lhs.protocol = EPM_PROTOCOL_IP; + twr->tower.floors[4].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[4].rhs.ip.ipaddr = "0.0.0.0"; + + status = dcerpc_epm_Map_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && r.out.result == 0) { + for (i=0;i<*r.out.num_towers;i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &r.out.towers[i].twr->tower); + } + } + } + + twr->tower.floors[3].lhs.protocol = EPM_PROTOCOL_HTTP; + twr->tower.floors[3].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[3].rhs.http.port = 0; + + status = dcerpc_epm_Map_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && r.out.result == 0) { + for (i=0;i<*r.out.num_towers;i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &r.out.towers[i].twr->tower); + } + } + } + + twr->tower.floors[3].lhs.protocol = EPM_PROTOCOL_UDP; + twr->tower.floors[3].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[3].rhs.udp.port = 0; + + status = dcerpc_epm_Map_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && r.out.result == 0) { + for (i=0;i<*r.out.num_towers;i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &r.out.towers[i].twr->tower); + } + } + } + + twr->tower.floors[3].lhs.protocol = EPM_PROTOCOL_SMB; + twr->tower.floors[3].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[3].rhs.smb.unc = ""; + + twr->tower.floors[4].lhs.protocol = EPM_PROTOCOL_NETBIOS; + twr->tower.floors[4].lhs.lhs_data = data_blob(NULL, 0); + twr->tower.floors[4].rhs.netbios.name = ""; + + status = dcerpc_epm_Map_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && r.out.result == 0) { + for (i = 0; i < *r.out.num_towers; i++) { + if (r.out.towers[i].twr) { + display_tower(tctx, &r.out.towers[i].twr->tower); + } + } + } + + /* FIXME: Extend to do other protocols as well (ncacn_unix_stream, ncalrpc) */ + + return true; +} + +static bool test_Map_simple(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct epm_Lookup r; + struct policy_handle entry_handle; + uint32_t num_ents = 0; + struct dcerpc_binding_handle *h = p->binding_handle; + + ZERO_STRUCT(entry_handle); + + torture_comment(tctx, "Testing epm_Map\n"); + + /* get all elements */ + r.in.inquiry_type = RPC_C_EP_ALL_ELTS; + r.in.object = NULL; + r.in.interface_id = NULL; + r.in.vers_option = RPC_C_VERS_ALL; + + r.in.entry_handle = &entry_handle; + r.in.max_ents = 10; + + r.out.entry_handle = &entry_handle; + r.out.num_ents = &num_ents; + + do { + int i; + + status = dcerpc_epm_Lookup_r(h, tctx, &r); + if (!NT_STATUS_IS_OK(status) || + r.out.result != EPMAPPER_STATUS_OK) { + break; + } + + for (i = 0; i < *r.out.num_ents; i++) { + if (r.out.entries[i].tower->tower.num_floors == 5) { + test_Map_display(h, tctx, &r.out.entries[i]); + } + } + } while (NT_STATUS_IS_OK(status) && + r.out.result == EPMAPPER_STATUS_OK && + *r.out.num_ents == r.in.max_ents && + !ndr_policy_handle_empty(&entry_handle)); + + torture_assert_ntstatus_ok(tctx, status, "epm_Map_simple failed"); + + torture_assert(tctx, + ndr_policy_handle_empty(&entry_handle), + "epm_Map_simple failed - The policy handle should be empty."); + + return true; +} + +static bool test_LookupHandleFree(struct torture_context *tctx, + struct dcerpc_binding_handle *h, + struct policy_handle *entry_handle) { + NTSTATUS status; + struct epm_LookupHandleFree r; + + if (ndr_policy_handle_empty(entry_handle)) { + torture_comment(tctx, + "epm_LookupHandleFree failed - empty policy_handle\n"); + return false; + } + + r.in.entry_handle = entry_handle; + r.out.entry_handle = entry_handle; + + status = dcerpc_epm_LookupHandleFree_r(h, tctx, &r); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, + "epm_LookupHandleFree failed - %s\n", + nt_errstr(status)); + return false; + } + + if (r.out.result != EPMAPPER_STATUS_OK) { + torture_comment(tctx, + "epm_LookupHandleFree failed - internal error: " + "0x%.4x\n", + r.out.result); + return false; + } + + return true; +} + +static bool test_Lookup_simple(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct epm_Lookup r; + struct policy_handle entry_handle; + uint32_t num_ents = 0; + struct dcerpc_binding_handle *h = p->binding_handle; + + ZERO_STRUCT(entry_handle); + + torture_comment(tctx, "Testing epm_Lookup\n"); + + /* get all elements */ + r.in.inquiry_type = RPC_C_EP_ALL_ELTS; + r.in.object = NULL; + r.in.interface_id = NULL; + r.in.vers_option = RPC_C_VERS_ALL; + + r.in.entry_handle = &entry_handle; + r.in.max_ents = 10; + + r.out.entry_handle = &entry_handle; + r.out.num_ents = &num_ents; + + do { + int i; + + status = dcerpc_epm_Lookup_r(h, tctx, &r); + if (!NT_STATUS_IS_OK(status) || + r.out.result != EPMAPPER_STATUS_OK) { + break; + } + + torture_comment(tctx, + "epm_Lookup returned %d events, entry_handle: %s\n", + *r.out.num_ents, + GUID_string(tctx, &entry_handle.uuid)); + + for (i = 0; i < *r.out.num_ents; i++) { + torture_comment(tctx, + "\n Found '%s' Object[%s]\n", + r.out.entries[i].annotation, + GUID_string(tctx, &r.out.entries[i].object)); + + display_tower(tctx, &r.out.entries[i].tower->tower); + } + } while (NT_STATUS_IS_OK(status) && + r.out.result == EPMAPPER_STATUS_OK && + *r.out.num_ents == r.in.max_ents && + !ndr_policy_handle_empty(&entry_handle)); + + torture_assert_ntstatus_ok(tctx, status, "epm_Lookup failed"); + torture_assert(tctx, r.out.result == EPMAPPER_STATUS_NO_MORE_ENTRIES, "epm_Lookup failed"); + + torture_assert(tctx, + ndr_policy_handle_empty(&entry_handle), + "epm_Lookup failed - The policy handle should be empty."); + + return true; +} + +/* + * This test starts a epm_Lookup request, but doesn't finish the + * call terminates the search. So it will call epm_LookupHandleFree. + */ +static bool test_Lookup_terminate_search(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + bool ok; + NTSTATUS status; + struct epm_Lookup r; + struct policy_handle entry_handle; + uint32_t i, num_ents = 0; + struct dcerpc_binding_handle *h = p->binding_handle; + + ZERO_STRUCT(entry_handle); + + torture_comment(tctx, "Testing epm_Lookup and epm_LookupHandleFree\n"); + + /* get all elements */ + r.in.inquiry_type = RPC_C_EP_ALL_ELTS; + r.in.object = NULL; + r.in.interface_id = NULL; + r.in.vers_option = RPC_C_VERS_ALL; + + r.in.entry_handle = &entry_handle; + r.in.max_ents = 2; + + r.out.entry_handle = &entry_handle; + r.out.num_ents = &num_ents; + + status = dcerpc_epm_Lookup_r(h, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "epm_Lookup failed"); + torture_assert(tctx, r.out.result == EPMAPPER_STATUS_OK, "epm_Lookup failed"); + + torture_comment(tctx, + "epm_Lookup returned %d events, entry_handle: %s\n", + *r.out.num_ents, + GUID_string(tctx, &entry_handle.uuid)); + + for (i = 0; i < *r.out.num_ents; i++) { + torture_comment(tctx, + "\n Found '%s'\n", + r.out.entries[i].annotation); + } + + ok = test_LookupHandleFree(tctx, + h, + &entry_handle); + if (!ok) { + return false; + } + + return true; +} + +static bool test_Insert_noreplace(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + bool ok; + NTSTATUS status; + struct epm_Insert r; + struct dcerpc_binding *b; + struct dcerpc_binding_handle *h = p->binding_handle; + + torture_comment(tctx, "Testing epm_Insert(noreplace) and epm_Delete\n"); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "Skip Insert test against Samba4"); + } + + r.in.num_ents = 1; + r.in.entries = talloc_array(tctx, struct epm_entry_t, 1); + + ZERO_STRUCT(r.in.entries[0].object); + r.in.entries[0].annotation = "smbtorture endpoint"; + + status = dcerpc_parse_binding(tctx, "ncalrpc:[SMBTORTURE]", &b); + torture_assert_ntstatus_ok(tctx, + status, + "Unable to generate dcerpc_binding struct"); + + r.in.entries[0].tower = talloc(tctx, struct epm_twr_t); + + status = dcerpc_binding_build_tower(tctx, + b, + &r.in.entries[0].tower->tower); + torture_assert_ntstatus_ok(tctx, + status, + "Unable to build tower from binding struct"); + r.in.replace = 0; + + status = dcerpc_epm_Insert_r(h, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "epm_Insert failed"); + + torture_assert(tctx, r.out.result == 0, "epm_Insert failed"); + + ok = test_Delete(tctx, h, "smbtorture", b); + if (!ok) { + return false; + } + + return true; +} + +#if 0 +/* + * The MS-RPCE documentation states that this function isn't implemented and + * SHOULD NOT be called by a client. + */ +static bool test_InqObject(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct epm_InqObject r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.epm_object = talloc(tctx, struct GUID); + *r.in.epm_object = ndr_table_epmapper.syntax_id.uuid; + + status = dcerpc_epm_InqObject_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "InqObject failed"); + + return true; +} +#endif + +struct torture_suite *torture_rpc_epmapper(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "epmapper"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, + "epmapper", + &ndr_table_epmapper); + + /* This is a stack */ + torture_rpc_tcase_add_test(tcase, + "Map_simple", + test_Map_simple); + torture_rpc_tcase_add_test(tcase, + "Map_full", + test_Map_full); + torture_rpc_tcase_add_test(tcase, + "Lookup_simple", + test_Lookup_simple); + torture_rpc_tcase_add_test(tcase, + "Lookup_terminate_search", + test_Lookup_terminate_search); + torture_rpc_tcase_add_test(tcase, + "Insert_noreplace", + test_Insert_noreplace); + + return suite; +} + +/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */ diff --git a/source4/torture/rpc/eventlog.c b/source4/torture/rpc/eventlog.c new file mode 100644 index 0000000..87dc6a2 --- /dev/null +++ b/source4/torture/rpc/eventlog.c @@ -0,0 +1,501 @@ +/* + Unix SMB/CIFS implementation. + test suite for eventlog rpc operations + + Copyright (C) Tim Potter 2003,2005 + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Guenther Deschner 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_eventlog.h" +#include "librpc/gen_ndr/ndr_eventlog_c.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + +#define TEST_BACKUP_NAME "samrtorturetest" + +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; + name->length = 2*strlen_m(s); + name->size = name->length; +} + +static bool get_policy_handle(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct eventlog_OpenEventLogW r; + struct eventlog_OpenUnknown0 unknown0; + struct lsa_String logname, servername; + + unknown0.unknown0 = 0x005c; + unknown0.unknown1 = 0x0001; + + r.in.unknown0 = &unknown0; + init_lsa_String(&logname, "dns server"); + init_lsa_String(&servername, NULL); + r.in.logname = &logname; + r.in.servername = &servername; + r.in.major_version = 0x00000001; + r.in.minor_version = 0x00000001; + r.out.handle = handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_OpenEventLogW_r(b, tctx, &r), + "OpenEventLog failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "OpenEventLog failed"); + + return true; +} + + + +static bool test_GetNumRecords(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct eventlog_GetNumRecords r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + uint32_t number = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + ZERO_STRUCT(r); + r.in.handle = &handle; + r.out.number = &number; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_GetNumRecords_r(b, tctx, &r), + "GetNumRecords failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "GetNumRecords failed"); + torture_comment(tctx, "%d records\n", *r.out.number); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + return true; +} + +static bool test_ReadEventLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct eventlog_ReadEventLogW r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t sent_size = 0; + uint32_t real_size = 0; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + ZERO_STRUCT(r); + r.in.offset = 0; + r.in.handle = &handle; + r.in.flags = 0; + r.out.data = NULL; + r.out.sent_size = &sent_size; + r.out.real_size = &real_size; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_ReadEventLogW_r(b, tctx, &r), + "ReadEventLog failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_PARAMETER, + "ReadEventLog failed"); + + while (1) { + struct EVENTLOGRECORD rec; + enum ndr_err_code ndr_err; + uint32_t size = 0; + uint32_t pos = 0; + + /* Read first for number of bytes in record */ + + r.in.number_of_bytes = 0; + r.in.flags = EVENTLOG_BACKWARDS_READ|EVENTLOG_SEQUENTIAL_READ; + r.out.data = NULL; + r.out.sent_size = &sent_size; + r.out.real_size = &real_size; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_ReadEventLogW_r(b, tctx, &r), + "ReadEventLogW failed"); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_END_OF_FILE)) { + /* FIXME: still need to decode then */ + break; + } + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_BUFFER_TOO_SMALL, + "ReadEventLog failed"); + + /* Now read the actual record */ + + r.in.number_of_bytes = *r.out.real_size; + r.out.data = talloc_array(tctx, uint8_t, r.in.number_of_bytes); + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_ReadEventLogW_r(b, tctx, &r), + "ReadEventLogW failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "ReadEventLog failed"); + + /* Decode a user-marshalled record */ + size = IVAL(r.out.data, pos); + + while (size > 0) { + DATA_BLOB blob = data_blob_const( + r.out.data + pos, size); + dump_data(0, blob.data, blob.length); + + ndr_err = ndr_pull_struct_blob_all(&blob, tctx, &rec, + (ndr_pull_flags_fn_t)ndr_pull_EVENTLOGRECORD); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + torture_assert_ntstatus_ok(tctx, status, + "ReadEventLog failed parsing event log record"); + } + + NDR_PRINT_DEBUG(EVENTLOGRECORD, &rec); + + pos += size; + + if (pos + 4 > *r.out.sent_size) { + break; + } + + size = IVAL(r.out.data, pos); + } + + torture_assert_ntstatus_ok(tctx, r.out.result, + "ReadEventLog failed parsing event log record"); + + r.in.offset++; + } + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +static bool test_ReportEventLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct eventlog_ReportEventW r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t record_number = 0; + time_t time_written = 0; + struct lsa_String servername, *strings; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + init_lsa_String(&servername, NULL); + + strings = talloc_array(tctx, struct lsa_String, 1); + init_lsa_String(&strings[0], "Currently tortured by samba 4"); + + ZERO_STRUCT(r); + + r.in.handle = &handle; + r.in.timestamp = time(NULL); + r.in.event_type = EVENTLOG_INFORMATION_TYPE; + r.in.event_category = 0; + r.in.event_id = 0; + r.in.num_of_strings = 1; + r.in.data_size = 0; + r.in.servername = &servername; + r.in.user_sid = NULL; + r.in.strings = &strings; + r.in.data = NULL; + r.in.flags = 0; + r.in.record_number = r.out.record_number = &record_number; + r.in.time_written = r.out.time_written = &time_written; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_ReportEventW_r(b, tctx, &r), + "ReportEventW failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "ReportEventW failed"); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +static bool test_FlushEventLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct eventlog_FlushEventLog r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + r.in.handle = &handle; + + /* Huh? Does this RPC always return access denied? */ + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_FlushEventLog_r(b, tctx, &r), + "FlushEventLog failed"); + + torture_assert_ntstatus_equal(tctx, + r.out.result, + NT_STATUS_ACCESS_DENIED, + "FlushEventLog failed"); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +static bool test_ClearEventLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct eventlog_ClearEventLogW r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + r.in.handle = &handle; + r.in.backupfile = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_ClearEventLogW_r(b, tctx, &r), + "ClearEventLog failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "ClearEventLog failed"); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +static bool test_GetLogInformation(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct eventlog_GetLogInformation r; + struct eventlog_CloseEventLog cr; + struct policy_handle handle; + uint32_t bytes_needed = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + r.in.handle = &handle; + r.in.level = 1; + r.in.buf_size = 0; + r.out.buffer = NULL; + r.out.bytes_needed = &bytes_needed; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_GetLogInformation_r(b, tctx, &r), + "GetLogInformation failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_LEVEL, + "GetLogInformation failed"); + + r.in.level = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_GetLogInformation_r(b, tctx, &r), + "GetLogInformation failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_BUFFER_TOO_SMALL, + "GetLogInformation failed"); + + r.in.buf_size = bytes_needed; + r.out.buffer = talloc_array(tctx, uint8_t, bytes_needed); + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_GetLogInformation_r(b, tctx, &r), + "GetLogInformation failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "GetLogInformation failed"); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + + +static bool test_OpenEventLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct policy_handle handle; + struct eventlog_CloseEventLog cr; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +static bool test_BackupLog(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct policy_handle handle, backup_handle; + struct eventlog_BackupEventLogW r; + struct eventlog_OpenBackupEventLogW br; + struct eventlog_CloseEventLog cr; + const char *tmp; + struct lsa_String backup_filename; + struct eventlog_OpenUnknown0 unknown0; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping BackupLog test against samba"); + } + + if (!get_policy_handle(tctx, b, &handle)) + return false; + + tmp = talloc_asprintf(tctx, "C:\\%s", TEST_BACKUP_NAME); + init_lsa_String(&backup_filename, tmp); + + r.in.handle = &handle; + r.in.backup_filename = &backup_filename; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_BackupEventLogW_r(b, tctx, &r), + "BackupEventLogW failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, + NT_STATUS_OBJECT_PATH_SYNTAX_BAD, "BackupEventLogW failed"); + + tmp = talloc_asprintf(tctx, "\\??\\C:\\%s", TEST_BACKUP_NAME); + init_lsa_String(&backup_filename, tmp); + + r.in.handle = &handle; + r.in.backup_filename = &backup_filename; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_BackupEventLogW_r(b, tctx, &r), + "BackupEventLogW failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "BackupEventLogW failed"); + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_BackupEventLogW_r(b, tctx, &r), + "BackupEventLogW failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, + NT_STATUS_OBJECT_NAME_COLLISION, "BackupEventLogW failed"); + + cr.in.handle = cr.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "BackupLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "BackupLog failed"); + + unknown0.unknown0 = 0x005c; + unknown0.unknown1 = 0x0001; + + br.in.unknown0 = &unknown0; + br.in.backup_logname = &backup_filename; + br.in.major_version = 1; + br.in.minor_version = 1; + br.out.handle = &backup_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_eventlog_OpenBackupEventLogW_r(b, tctx, &br), + "OpenBackupEventLogW failed"); + + torture_assert_ntstatus_ok(tctx, br.out.result, "OpenBackupEventLogW failed"); + + cr.in.handle = cr.out.handle = &backup_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_eventlog_CloseEventLog_r(b, tctx, &cr), + "CloseEventLog failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, + "CloseEventLog failed"); + + return true; +} + +struct torture_suite *torture_rpc_eventlog(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + struct torture_test *test; + + suite = torture_suite_create(mem_ctx, "eventlog"); + tcase = torture_suite_add_rpc_iface_tcase(suite, "eventlog", + &ndr_table_eventlog); + + torture_rpc_tcase_add_test(tcase, "OpenEventLog", test_OpenEventLog); + test = torture_rpc_tcase_add_test(tcase, "ClearEventLog", + test_ClearEventLog); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "GetNumRecords", test_GetNumRecords); + torture_rpc_tcase_add_test(tcase, "ReadEventLog", test_ReadEventLog); + torture_rpc_tcase_add_test(tcase, "ReportEventLog", test_ReportEventLog); + torture_rpc_tcase_add_test(tcase, "FlushEventLog", test_FlushEventLog); + torture_rpc_tcase_add_test(tcase, "GetLogIntormation", test_GetLogInformation); + torture_rpc_tcase_add_test(tcase, "BackupLog", test_BackupLog); + + return suite; +} diff --git a/source4/torture/rpc/forest_trust.c b/source4/torture/rpc/forest_trust.c new file mode 100644 index 0000000..ceb1a7e --- /dev/null +++ b/source4/torture/rpc/forest_trust.c @@ -0,0 +1,914 @@ +/* + Unix SMB/CIFS implementation. + test suite for forest trust + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Sumit Bose <sbose@redhat.com> 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 "torture/torture.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "libcli/security/security.h" +#include "libcli/auth/credentials.h" +#include "libcli/auth/libcli_auth.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef strcasecmp + +#define TEST_DOM "torturedom" +#define TEST_DOM_DNS "torturedom.samba.example.com" +#define TEST_DOM_SID "S-1-5-21-97398-379795-10000" +#define TEST_MACHINE_NAME "lsatestmach" + + +static bool test_get_policy_handle(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint32_t access_mask, + struct policy_handle **handle ) +{ + struct policy_handle *h; + struct lsa_OpenPolicy2 pr; + struct lsa_ObjectAttribute attr; + NTSTATUS status; + + h = talloc(tctx, struct policy_handle); + torture_assert(tctx, h != NULL, "talloc(tctx, struct policy_handle)"); + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = NULL; + + pr.in.system_name = "\\"; + pr.in.attr = &attr; + pr.in.access_mask = access_mask; + pr.out.handle = h; + + status = dcerpc_lsa_OpenPolicy2_r(p->binding_handle, tctx, &pr); + torture_assert_ntstatus_ok(tctx, status, "OpenPolicy2 failed"); + torture_assert_ntstatus_ok(tctx, pr.out.result, "OpenPolicy2 failed"); + + *handle = h; + return true; +} + +static bool test_create_trust_and_set_info(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *trust_name, + const char *trust_name_dns, + struct dom_sid *domsid, + struct lsa_TrustDomainInfoAuthInfoInternal *authinfo) +{ + struct policy_handle *handle; + struct lsa_lsaRSetForestTrustInformation fti; + struct lsa_ForestTrustCollisionInfo *collision_info = NULL; + struct lsa_Close cr; + struct policy_handle closed_handle; + struct lsa_CreateTrustedDomainEx2 r; + struct lsa_TrustDomainInfoInfoEx trustinfo; + struct policy_handle trustdom_handle; + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info = NULL; + + if (!test_get_policy_handle(tctx, p, + (LSA_POLICY_VIEW_LOCAL_INFORMATION | + LSA_POLICY_TRUST_ADMIN | + LSA_POLICY_CREATE_SECRET), &handle)) { + return false; + } + + torture_comment(tctx, "\nTesting CreateTrustedDomainEx2\n"); + + trustinfo.sid = domsid; + trustinfo.netbios_name.string = trust_name; + trustinfo.domain_name.string = trust_name_dns; + + trustinfo.trust_direction = LSA_TRUST_DIRECTION_INBOUND | + LSA_TRUST_DIRECTION_OUTBOUND; + + trustinfo.trust_type = LSA_TRUST_TYPE_UPLEVEL; + + /* + * MS-LSAD: Section 3.1.4.7.10 makes it clear that Win2k3 + * functional level and above return + * NT_STATUS_INVALID_DOMAIN_STATE if + * TRUST_ATTRIBUTE_FOREST_TRANSITIVE or + * TRUST_ATTRIBUTE_CROSS_ORGANIZATION is set here. + * + * But we really want to test forest trusts here. + */ + trustinfo.trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE; + + r.in.policy_handle = handle; + r.in.info = &trustinfo; + r.in.auth_info_internal = authinfo; + /* LSA_TRUSTED_QUERY_DOMAIN_NAME is needed for for following + * QueryTrustedDomainInfo call, although it seems that Windows does not + * expect this */ + r.in.access_mask = LSA_TRUSTED_SET_POSIX | LSA_TRUSTED_SET_AUTH | LSA_TRUSTED_QUERY_DOMAIN_NAME; + r.out.trustdom_handle = &trustdom_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_CreateTrustedDomainEx2_r(p->binding_handle, tctx, &r), + "CreateTrustedDomainEx2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "CreateTrustedDomainEx2 failed"); + + q.in.trustdom_handle = &trustdom_handle; + q.in.level = LSA_TRUSTED_DOMAIN_INFO_INFO_EX; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_QueryTrustedDomainInfo_r(p->binding_handle, tctx, &q), + "QueryTrustedDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, q.out.result, "QueryTrustedDomainInfo level 1"); + torture_assert(tctx, q.out.info != NULL, "QueryTrustedDomainInfo level 1 failed to return an info pointer"); + torture_assert_str_equal(tctx, info->info_ex.netbios_name.string, + trustinfo.netbios_name.string, + "QueryTrustedDomainInfo returned inconsistent short name"); + torture_assert_int_equal(tctx, info->info_ex.trust_type, trustinfo.trust_type, + "QueryTrustedDomainInfo returned incorrect trust type"); + torture_assert_int_equal(tctx, info->info_ex.trust_attributes, trustinfo.trust_attributes, + "QueryTrustedDomainInfo of returned incorrect trust attributes"); + torture_assert_int_equal(tctx, info->info_ex.trust_direction, trustinfo.trust_direction, + "QueryTrustedDomainInfo of returned incorrect trust direction"); + + fti.in.handle = handle; + fti.in.trusted_domain_name = talloc_zero(tctx, struct lsa_StringLarge); + fti.in.trusted_domain_name->string = trust_name_dns; + fti.in.highest_record_type = 2; + fti.in.forest_trust_info = talloc_zero(tctx, struct lsa_ForestTrustInformation); + fti.in.forest_trust_info->count = 2; + fti.in.forest_trust_info->entries = talloc_array(tctx, struct lsa_ForestTrustRecord *, 2); + fti.in.forest_trust_info->entries[0] = talloc_zero(tctx, struct lsa_ForestTrustRecord); + fti.in.forest_trust_info->entries[0]->flags = 0; + fti.in.forest_trust_info->entries[0]->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME; + fti.in.forest_trust_info->entries[0]->time = 0; + fti.in.forest_trust_info->entries[0]->forest_trust_data.top_level_name.string = trust_name_dns; + fti.in.forest_trust_info->entries[1] = talloc_zero(tctx, struct lsa_ForestTrustRecord); + fti.in.forest_trust_info->entries[1]->flags = 0; + fti.in.forest_trust_info->entries[1]->type = LSA_FOREST_TRUST_DOMAIN_INFO; + fti.in.forest_trust_info->entries[1]->time = 0; + fti.in.forest_trust_info->entries[1]->forest_trust_data.domain_info.domain_sid = domsid; + fti.in.forest_trust_info->entries[1]->forest_trust_data.domain_info.dns_domain_name.string = trust_name_dns; + fti.in.forest_trust_info->entries[1]->forest_trust_data.domain_info.netbios_domain_name.string = trust_name; + fti.in.check_only = 0; + fti.out.collision_info = &collision_info; + + torture_comment(tctx, "\nTesting SetForestTrustInformation\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_lsaRSetForestTrustInformation_r(p->binding_handle, tctx, &fti), + "lsaRSetForestTrustInformation failed"); + torture_assert_ntstatus_ok(tctx, fti.out.result, "lsaRSetForestTrustInformation failed"); + + cr.in.handle = handle; + cr.out.handle = &closed_handle; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_Close_r(p->binding_handle, tctx, &cr), + "Close failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, "Close failed"); + + return true; +} + +struct get_set_info { + enum lsa_TrustDomInfoEnum info_level; + NTSTATUS get_result; + NTSTATUS set_result; +}; + +static bool get_and_set_info(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *name) +{ + struct policy_handle *handle; + NTSTATUS status; + struct lsa_QueryTrustedDomainInfoByName qr; + struct lsa_SetTrustedDomainInfoByName sr; + union lsa_TrustedDomainInfo *info; + struct lsa_Close cr; + struct policy_handle closed_handle; + size_t c; + + struct get_set_info il[] = { + {LSA_TRUSTED_DOMAIN_INFO_NAME, NT_STATUS_OK, NT_STATUS_INVALID_PARAMETER}, + /* {LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + {LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET, NT_STATUS_OK, NT_STATUS_OK}, + /* {LSA_TRUSTED_DOMAIN_INFO_PASSWORD, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + /* {LSA_TRUSTED_DOMAIN_INFO_BASIC, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + {LSA_TRUSTED_DOMAIN_INFO_INFO_EX, NT_STATUS_OK, NT_STATUS_OK}, + /* {LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + {LSA_TRUSTED_DOMAIN_INFO_FULL_INFO, NT_STATUS_OK, NT_STATUS_OK}, + /* {LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + /* {LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + /* {LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL, NT_STATUS_INVALID_PARAMETER, NT_STATUS_INVALID_INFO_CLASS}, */ + {LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL, NT_STATUS_OK, NT_STATUS_INVALID_PARAMETER}, + {LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES, NT_STATUS_OK, NT_STATUS_OK}, + { .info_level = -1, }, + }; + + torture_comment(tctx, "\nGetting/Setting dom info\n"); + + if(!test_get_policy_handle(tctx, p, LSA_POLICY_VIEW_LOCAL_INFORMATION, + &handle)) { + return false; + } + + qr.in.handle = handle; + qr.in.trusted_domain = talloc_zero(tctx, struct lsa_String); + qr.in.trusted_domain->string = name; + qr.out.info = &info; + + sr.in.handle = handle; + sr.in.trusted_domain = talloc_zero(tctx, struct lsa_String); + sr.in.trusted_domain->string = name; + sr.in.info = info; + + for (c = 0; il[c].info_level != -1; c++) { + torture_comment(tctx, "\nGetting/Setting dom info [%d]\n",il[c].info_level); + + qr.in.level = il[c].info_level; + status = dcerpc_lsa_QueryTrustedDomainInfoByName_r(p->binding_handle, + tctx, &qr); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_OK, + "QueryTrustedDomainInfoByName failed"); + if (!NT_STATUS_EQUAL(qr.out.result, il[c].get_result)) { + torture_comment(tctx, "QueryTrustedDomainInfoByName did not return " + "%s but %s\n", + nt_errstr(il[c].get_result), + nt_errstr(qr.out.result)); + + /* We may be testing a server without support for this level */ + if (qr.in.level == LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES && NT_STATUS_EQUAL(qr.out.result, NT_STATUS_INVALID_PARAMETER)) { + return true; + } + return false; + } + + sr.in.level = il[c].info_level; + sr.in.info = info; + status = dcerpc_lsa_SetTrustedDomainInfoByName_r(p->binding_handle, + tctx, &sr); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_OK, + "SetTrustedDomainInfoByName failed"); + if (!NT_STATUS_EQUAL(sr.out.result, il[c].set_result)) { + torture_comment(tctx, "SetTrustedDomainInfoByName did not return " + "%s but %s\n", + nt_errstr(il[c].set_result), + nt_errstr(sr.out.result)); + return false; + } + } + + cr.in.handle = handle; + cr.out.handle = &closed_handle; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_Close_r(p->binding_handle, tctx, &cr), + "Close failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, "Close failed"); + + return true; +} + +static bool check_name(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *name) +{ + struct policy_handle *handle; + NTSTATUS status; + struct lsa_QueryTrustedDomainInfoByName qr; + union lsa_TrustedDomainInfo *info; + struct lsa_Close cr; + struct policy_handle closed_handle; + + torture_comment(tctx, "\nGetting LSA_TRUSTED_DOMAIN_INFO_FULL_INFO\n"); + + if(!test_get_policy_handle(tctx, p, LSA_POLICY_VIEW_LOCAL_INFORMATION, + &handle)) { + return false; + } + + qr.in.handle = handle; + qr.in.trusted_domain = talloc_zero(tctx, struct lsa_String); + qr.in.trusted_domain->string = name; + qr.in.level = LSA_TRUSTED_DOMAIN_INFO_FULL_INFO; + qr.out.info = &info; + status = dcerpc_lsa_QueryTrustedDomainInfoByName_r(p->binding_handle, + tctx, &qr); + torture_assert_ntstatus_ok(tctx, status, + "QueryInfoPolicy2 failed"); + torture_assert_ntstatus_equal(tctx, qr.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND, + "QueryInfoPolicy2 did not return " + "NT_STATUS_OBJECT_NAME_NOT_FOUND"); + + cr.in.handle = handle; + cr.out.handle = &closed_handle; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_Close_r(p->binding_handle, tctx, &cr), + "Close failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, "Close failed"); + + return true; +} + +static bool get_lsa_policy_info_dns(struct dcerpc_pipe *p, + struct torture_context *tctx, + union lsa_PolicyInformation **info) +{ + struct policy_handle *handle; + NTSTATUS status; + struct lsa_QueryInfoPolicy2 qr; + struct lsa_Close cr; + struct policy_handle closed_handle; + + torture_comment(tctx, "\nGetting LSA_POLICY_INFO_DNS\n"); + + if (!test_get_policy_handle(tctx, p, LSA_POLICY_VIEW_LOCAL_INFORMATION, + &handle)) { + return false; + } + + qr.in.handle = handle; + qr.in.level = LSA_POLICY_INFO_DNS; + qr.out.info = info; + status = dcerpc_lsa_QueryInfoPolicy2_r(p->binding_handle, tctx, &qr); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_OK, + "QueryInfoPolicy2 failed"); + if (!NT_STATUS_IS_OK(qr.out.result)) { + torture_comment(tctx, "QueryInfoPolicy2 failed - %s\n", + nt_errstr(qr.out.result)); + return false; + } + + cr.in.handle = handle; + cr.out.handle = &closed_handle; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_Close_r(p->binding_handle, tctx, &cr), + "Close failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, "Close failed"); + + return true; +} + +static bool delete_trusted_domain_by_sid(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct dom_sid *domsid) +{ + struct policy_handle *handle; + struct lsa_Close cr; + struct policy_handle closed_handle; + struct lsa_DeleteTrustedDomain dr; + + torture_comment(tctx, "\nDeleting trusted domain.\n"); + + /* Against a windows server it was sufficient to have + * LSA_POLICY_VIEW_LOCAL_INFORMATION although the documentations says + * otherwise. */ + if (!test_get_policy_handle(tctx, p, LSA_POLICY_TRUST_ADMIN, + &handle)) { + return false; + } + + dr.in.handle = handle; + dr.in.dom_sid = domsid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_DeleteTrustedDomain_r(p->binding_handle, tctx, &dr), + "DeleteTrustedDomain failed"); + torture_assert_ntstatus_ok(tctx, dr.out.result, "DeleteTrustedDomain failed"); + + cr.in.handle = handle; + cr.out.handle = &closed_handle; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_Close_r(p->binding_handle, tctx, &cr), + "Close failed"); + torture_assert_ntstatus_ok(tctx, cr.out.result, "Close failed"); + + return true; +} + +/* +static const uint8_t my_blob[] = { +0xa3,0x0b,0x32,0x45,0x8b,0x84,0x3b,0x01,0x68,0xe8,0x2b,0xbb,0x00,0x13,0x69,0x1f, +0x10,0x35,0x72,0xa9,0x4f,0x77,0xb7,0xeb,0x59,0x08,0x07,0xc3,0xe8,0x17,0x00,0xc5, +0xf2,0xa9,0x6d,0xb7,0x69,0x45,0x63,0x20,0xcb,0x44,0x44,0x22,0x02,0xe3,0x28,0x84, +0x9b,0xd5,0x43,0x6f,0x8d,0x36,0x9b,0x9b,0x3b,0x31,0x86,0x84,0x8b,0xf2,0x36,0xd4, +0xe8,0xc4,0xee,0x90,0x0c,0xcb,0x3e,0x11,0x2f,0x86,0xfe,0x87,0x6d,0xce,0xae,0x0c, +0x83,0xfb,0x21,0x22,0x6d,0x7f,0x5e,0x08,0x71,0x1a,0x35,0xf4,0x5a,0x76,0x9b,0xf7, +0x54,0x62,0xa5,0x4c,0xcd,0xf6,0xa5,0xb0,0x0b,0xc7,0x79,0xe1,0x6f,0x85,0x16,0x6f, +0x82,0xdd,0x15,0x11,0x4c,0x9d,0x26,0x01,0x74,0x7e,0xbb,0xec,0x88,0x1d,0x71,0x9e, +0x5f,0xb2,0x9c,0xab,0x66,0x20,0x08,0x3d,0xae,0x07,0x2d,0xbb,0xa6,0xfb,0xec,0xcc, +0x51,0x58,0x48,0x47,0x38,0x3b,0x47,0x66,0xe8,0x17,0xfa,0x54,0x5c,0x95,0x73,0x29, +0xdf,0x7e,0x4a,0xb4,0x45,0x30,0xf7,0xbf,0xc0,0x56,0x6d,0x80,0xf6,0x11,0x56,0x93, +0xeb,0x97,0xd5,0x10,0xd6,0xd6,0xf7,0x23,0xc3,0xc0,0x93,0xa7,0x5c,0xa9,0xc0,0x81, +0x55,0x3d,0xec,0x03,0x31,0x7e,0x9d,0xf9,0xd0,0x9e,0xb5,0xc7,0xef,0xa8,0x54,0xf6, +0x9c,0xdc,0x0d,0xd4,0xd7,0xee,0x8d,0x5f,0xbd,0x89,0x48,0x3b,0x63,0xff,0xe8,0xca, +0x10,0x64,0x61,0xdf,0xfd,0x50,0xff,0x51,0xa0,0x2c,0xd7,0x8a,0xf1,0x13,0x02,0x02, +0x71,0xe9,0xff,0x0d,0x03,0x48,0xf8,0x08,0x8d,0xd5,0xe6,0x31,0x9f,0xf0,0x26,0x07, +0x91,0x6d,0xd3,0x01,0x91,0x92,0xc7,0x28,0x18,0x58,0xd8,0xf6,0x1b,0x97,0x8d,0xd0, +0xd2,0xa1,0x7c,0xae,0xc1,0xca,0xfe,0x20,0x91,0x1c,0x4d,0x15,0x89,0x29,0x37,0xd5, +0xf5,0xca,0x40,0x2b,0x03,0x8f,0x7b,0xc2,0x10,0xb4,0xd3,0xe8,0x14,0xb0,0x9b,0x5d, +0x85,0x30,0xe5,0x13,0x24,0xf7,0x78,0xec,0xbe,0x0b,0x9a,0x3f,0xb5,0x76,0xd9,0x0d, +0x49,0x64,0xa4,0xa7,0x33,0x88,0xdd,0xe9,0xe2,0x5f,0x04,0x51,0xdd,0x89,0xe2,0x68, +0x5b,0x5f,0x64,0x35,0xe3,0x23,0x4a,0x0e,0x09,0x15,0xcc,0x97,0x47,0xf4,0xc2,0x4f, +0x06,0xc3,0x96,0xa9,0x2f,0xb3,0xde,0x29,0x10,0xc7,0xf5,0x16,0xc5,0x3c,0x84,0xd2, +0x9b,0x6b,0xaa,0x54,0x59,0x8d,0x94,0xde,0xd1,0x75,0xb6,0x08,0x0d,0x7d,0xf1,0x18, +0xc8,0xf5,0xdf,0xaa,0xcd,0xec,0xab,0xb6,0xd1,0xcb,0xdb,0xe7,0x75,0x5d,0xbe,0x76, +0xea,0x1d,0x01,0xc8,0x0b,0x2d,0x32,0xe9,0xa8,0x65,0xbb,0x4a,0xcb,0x72,0xbc,0xda, +0x04,0x7f,0x82,0xfb,0x04,0xeb,0xd8,0xe1,0xb9,0xb1,0x1e,0xdc,0xb3,0x60,0xf3,0x55, +0x1e,0xcf,0x90,0x6a,0x15,0x74,0x4d,0xff,0xb4,0xc7,0xc9,0xc2,0x4f,0x67,0x9e,0xeb, +0x00,0x61,0x02,0xe3,0x9e,0x59,0x88,0x20,0xf1,0x0c,0xbe,0xe0,0x26,0x69,0x63,0x67, +0x72,0x3c,0x06,0x00,0x9e,0x4f,0xc7,0xa6,0x4d,0x6c,0xbe,0x68,0x8e,0xf4,0x32,0x36, +0x2e,0x5f,0xa6,0xcf,0xa7,0x19,0x40,0x2b,0xbd,0xa2,0x22,0x73,0xc4,0xb6,0xe3,0x86, +0x64,0xeb,0xb1,0xc7,0x45,0x7d,0xd6,0xd9,0x36,0xf1,0x04,0xd4,0x61,0xdc,0x41,0xb7, +0x01,0x00,0x00,0x00,0x0c,0x00,0x00,0x00, 0x30,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x02,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x31,0x00,0x32,0x00,0x33,0x00,0x34,0x00, +0x35,0x00,0x36,0x00,0x37,0x00,0x38,0x00,0x39,0x00,0x30,0x00,0x01,0x00,0x00,0x00, +0x0c,0x00,0x00,0x00, 0x30,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00, +0x14,0x00,0x00,0x00,0x31,0x00,0x32,0x00,0x33,0x00,0x34,0x00,0x35,0x00,0x36,0x00, +0x37,0x00,0x38,0x00,0x39,0x00,0x30,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00 +}; +*/ +static bool get_trust_domain_passwords_auth_blob(TALLOC_CTX *mem_ctx, + const char *password, + DATA_BLOB *auth_blob) +{ + struct trustDomainPasswords auth_struct; + struct AuthenticationInformation *auth_info_array; + enum ndr_err_code ndr_err; + size_t converted_size; + + generate_random_buffer(auth_struct.confounder, + sizeof(auth_struct.confounder)); + + auth_info_array = talloc_array(mem_ctx, + struct AuthenticationInformation, 1); + if (auth_info_array == NULL) { + return false; + } + + auth_info_array[0].AuthType = TRUST_AUTH_TYPE_CLEAR; + if (!convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, password, + strlen(password), + &auth_info_array[0].AuthInfo.clear.password, + &converted_size)) { + return false; + } + + auth_info_array[0].AuthInfo.clear.size = converted_size; + + auth_struct.outgoing.count = 1; + auth_struct.outgoing.current.count = 1; + auth_struct.outgoing.current.array = auth_info_array; + auth_struct.outgoing.previous.count = 0; + auth_struct.outgoing.previous.array = NULL; + + auth_struct.incoming.count = 1; + auth_struct.incoming.current.count = 1; + auth_struct.incoming.current.array = auth_info_array; + auth_struct.incoming.previous.count = 0; + auth_struct.incoming.previous.array = NULL; + + ndr_err = ndr_push_struct_blob(auth_blob, mem_ctx, &auth_struct, + (ndr_push_flags_fn_t)ndr_push_trustDomainPasswords); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + + return true; +} + +static bool test_validate_trust(struct torture_context *tctx, + const char *binding, + const char *trusting_dom_name, + const char *trusting_dom_dns_name, + const char *trusted_dom_name, + const char *trusted_dom_dns_name, + const char *trust_password) +{ + struct netr_ServerGetTrustInfo r; + + struct netr_Authenticator a; + struct netr_Authenticator return_authenticator; + struct samr_Password new_owf_password; + struct samr_Password old_owf_password; + struct netr_TrustInfo *trust_info; + + struct netlogon_creds_CredentialState *creds; + + NTSTATUS status; + struct cli_credentials *credentials; + struct dcerpc_binding *b; + struct dcerpc_pipe *p1 = NULL; + struct dcerpc_pipe *p = NULL; + + struct netr_GetForestTrustInformation fr; + struct lsa_ForestTrustInformation *forest_trust_info; + struct lsa_ForestTrustRecord *tln = NULL; + struct lsa_ForestTrustRecord *di = NULL; + int i; + struct samr_Password *new_nt_hash; + struct samr_Password *old_nt_hash; + char *dummy; + uint32_t trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE; + + status = dcerpc_parse_binding(tctx, binding, &b); + torture_assert_ntstatus_ok(tctx, status, "Bad binding string"); + + credentials = cli_credentials_init(tctx); + torture_assert(tctx, credentials != NULL, "cli_credentials_init()"); + + dummy = talloc_asprintf(tctx, "%s$", trusted_dom_name); + cli_credentials_set_username(credentials, dummy, + CRED_SPECIFIED); + cli_credentials_set_domain(credentials, trusting_dom_name, + CRED_SPECIFIED); + cli_credentials_set_realm(credentials, trusting_dom_dns_name, + CRED_SPECIFIED); + cli_credentials_set_password(credentials, trust_password, CRED_SPECIFIED); + cli_credentials_set_old_password(credentials, trust_password, CRED_SPECIFIED); + cli_credentials_set_workstation(credentials, + trusted_dom_name, CRED_SPECIFIED); + cli_credentials_set_secure_channel_type(credentials, SEC_CHAN_DOMAIN); + + status = dcerpc_pipe_connect_b(tctx, &p1, b, + &ndr_table_netlogon, credentials, + tctx->ev, tctx->lp_ctx); + + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, "Failed to connect to remote server: %s with %s - %s\n", + binding, + cli_credentials_get_unparsed_name(credentials, tctx), + nt_errstr(status)); + return false; + } + + if (!test_SetupCredentials3(p1, tctx, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + credentials, &creds)) { + torture_comment(tctx, "test_SetupCredentials3 failed.\n"); + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + torture_comment(tctx, "test_SetupCredentialsPipe failed.\n"); + return false; + } + + netlogon_creds_client_authenticator(creds, &a); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", trusted_dom_name); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(credentials); + r.in.computer_name = trusted_dom_name; + r.in.credential = &a; + + r.out.return_authenticator = &return_authenticator; + r.out.new_owf_password = &new_owf_password; + r.out.old_owf_password = &old_owf_password; + r.out.trust_info = &trust_info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_ServerGetTrustInfo_r(p->binding_handle, tctx, &r), + "ServerGetTrustInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "ServerGetTrustInfo failed"); + + torture_assert(tctx, trust_info != NULL, "ServerGetTrustInfo got no trust_info"); + torture_assert_int_equal(tctx, trust_info->count, 1, + "Unexpected number of results"); + torture_assert_int_equal(tctx, trust_info->data[0], trust_attributes, + "Unexpected trust_attributes"); + + new_nt_hash = cli_credentials_get_nt_hash(credentials, tctx); + torture_assert(tctx, new_nt_hash != NULL, "cli_credentials_get_nt_hash()"); + + old_nt_hash = cli_credentials_get_old_nt_hash(credentials, tctx); + torture_assert(tctx, old_nt_hash != NULL, "cli_credentials_get_old_nt_hash()"); + + netlogon_creds_des_decrypt(creds, &new_owf_password); + netlogon_creds_des_decrypt(creds, &old_owf_password); + + dump_data(1, new_owf_password.hash, 16); + dump_data(1, new_nt_hash->hash, 16); + dump_data(1, old_owf_password.hash, 16); + dump_data(1, old_nt_hash->hash, 16); + + torture_assert_mem_equal(tctx, new_owf_password.hash, new_nt_hash->hash, 16, + "received unexpected new owf password\n"); + + torture_assert_mem_equal(tctx, old_owf_password.hash, old_nt_hash->hash, 16, + "received unexpected old owf password\n"); + + netlogon_creds_client_authenticator(creds, &a); + + fr.in.server_name = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + fr.in.computer_name = trusted_dom_name; + fr.in.credential = &a; + fr.in.flags = 0; + fr.out.return_authenticator = &return_authenticator; + fr.out.forest_trust_info = &forest_trust_info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_GetForestTrustInformation_r(p->binding_handle, tctx, &fr), + "netr_GetForestTrustInformation failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "netr_GetForestTrustInformation failed"); + + for(i = 0; i < forest_trust_info->count; i++) { + struct lsa_ForestTrustRecord *e = forest_trust_info->entries[i]; + + switch (e->type) { + case LSA_FOREST_TRUST_TOP_LEVEL_NAME: + if (strcmp(e->forest_trust_data.top_level_name.string, trusting_dom_dns_name) != 0) { + break; + } + + torture_assert(tctx, tln == NULL, "TOP_LEVEL_NAME found twice"); + + tln = e; + break; + + case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + break; + + case LSA_FOREST_TRUST_DOMAIN_INFO: + if (strcmp(e->forest_trust_data.domain_info.dns_domain_name.string, trusting_dom_dns_name) != 0) { + break; + } + + torture_assert(tctx, di == NULL, "DOMAIN_INFO found twice"); + + di = e; + break; + default: + torture_assert_int_equal(tctx, e->type, LSA_FOREST_TRUST_TOP_LEVEL_NAME, + "Unexptected LSA_FOREST_TRUST_* type"); + } + } + + torture_assert(tctx, tln != NULL, "TOP_LEVEL_NAME entry missing"); + torture_assert(tctx, di != NULL, "DOMAIN_INFO entry missing"); + + torture_assert_str_equal(tctx, di->forest_trust_data.domain_info.netbios_domain_name.string, + trusting_dom_name, + "netbios_domain_name mismatch"); + + return true; +} + +static bool test_setup_trust(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *netbios_name, + const char *dns_name, + struct dom_sid *sid, + DATA_BLOB *auth_blob) + +{ + DATA_BLOB session_key; + struct lsa_TrustDomainInfoAuthInfoInternal authinfo; + NTSTATUS status; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + + if (!check_name(p, tctx, netbios_name)) { + return false; + } + if (!check_name(p, tctx, dns_name)) { + return false; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed - %s\n", + nt_errstr(status)); + return false; + } + + authinfo.auth_blob.data = talloc_memdup(tctx, auth_blob->data, + auth_blob->length); + if (authinfo.auth_blob.data == NULL) { + return false; + } + authinfo.auth_blob.size = auth_blob->length; + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, + authinfo.auth_blob.data, + authinfo.auth_blob.size); + gnutls_cipher_deinit(cipher_hnd); + + if (!test_create_trust_and_set_info(p, tctx, netbios_name, + dns_name, sid, &authinfo)) { + return false; + } + + return true; +} + +static bool testcase_ForestTrusts(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const char *dom2_binding_string; + const char * dom2_cred_string; + NTSTATUS status; + struct dom_sid *domsid; + DATA_BLOB auth_blob; + struct dcerpc_binding *dom2_binding; + struct dcerpc_pipe *dom2_p; + struct cli_credentials *dom2_credentials; + union lsa_PolicyInformation *dom1_info_dns = NULL; + union lsa_PolicyInformation *dom2_info_dns = NULL; + const char *binding = torture_setting_string(tctx, "binding", NULL); + char *test_password; + + torture_comment(tctx, "Testing Forest Trusts\n"); + + test_password = generate_random_password(tctx, 32, 64); + torture_assert(tctx, test_password != NULL, "test password must be generated"); + + if (!get_trust_domain_passwords_auth_blob(tctx, test_password, &auth_blob)) { + torture_comment(tctx, + "get_trust_domain_passwords_auth_blob failed\n"); + return false; + } + +#if 0 + /* Use the following if get_trust_domain_passwords_auth_blob() cannot + * generate a usable blob due to errors in the IDL */ + auth_blob.data = talloc_memdup(tctx, my_blob, sizeof(my_blob)); + auth_blob.length = sizeof(my_blob); + + test_password = "1234567890" +#endif + + domsid = dom_sid_parse_talloc(tctx, TEST_DOM_SID); + if (domsid == NULL) { + return false; + } + + if (!test_setup_trust(tctx, p, TEST_DOM, TEST_DOM_DNS, domsid, + &auth_blob)) { + return false; + } + + if (!get_lsa_policy_info_dns(p, tctx, &dom1_info_dns)) { + return false; + } + + if (!get_and_set_info(p, tctx, TEST_DOM)) { + return false; + } + + if (!test_validate_trust(tctx, binding, + dom1_info_dns->dns.name.string, + dom1_info_dns->dns.dns_domain.string, + TEST_DOM, TEST_DOM_DNS, test_password)) { + return false; + } + + if (!delete_trusted_domain_by_sid(p, tctx, domsid)) { + return false; + } + + dom2_binding_string = torture_setting_string(tctx, + "Forest_Trust_Dom2_Binding", + NULL); + if (dom2_binding_string == NULL) { + torture_skip(tctx, "torture:Forest_Trust_Dom2_Binding not specified\n"); + } + + status = dcerpc_parse_binding(tctx, dom2_binding_string, &dom2_binding); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_parse_binding()"); + + dom2_cred_string = torture_setting_string(tctx, + "Forest_Trust_Dom2_Creds", + NULL); + torture_assert(tctx, dom2_cred_string != NULL, "torture:Forest_Trust_Dom2_Creds missing"); + + dom2_credentials = cli_credentials_init(tctx); + torture_assert(tctx, dom2_credentials != NULL, "cli_credentials_init()"); + + cli_credentials_parse_string(dom2_credentials, dom2_cred_string, + CRED_SPECIFIED); + cli_credentials_set_workstation(dom2_credentials, + TEST_MACHINE_NAME, CRED_SPECIFIED); + + status = dcerpc_pipe_connect_b(tctx, &dom2_p, dom2_binding, + &ndr_table_lsarpc, dom2_credentials, + tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, talloc_asprintf(tctx, + "Failed to connect to remote server: %s\n", + dcerpc_binding_string(tctx, dom2_binding))); + + if (!get_lsa_policy_info_dns(dom2_p, tctx, &dom2_info_dns)) { + return false; + } + + if (strcasecmp(dom1_info_dns->dns.name.string, + dom2_info_dns->dns.name.string) == 0 || + strcasecmp(dom1_info_dns->dns.dns_domain.string, + dom2_info_dns->dns.dns_domain.string) == 0) + { + torture_assert(tctx, false, talloc_asprintf(tctx, + "Trusting (%s;%s) and trusted domain (%s;%s) have the " + "same name", + dom1_info_dns->dns.name.string, + dom1_info_dns->dns.dns_domain.string, + dom2_info_dns->dns.name.string, + dom2_info_dns->dns.dns_domain.string)); + } + + if (!test_setup_trust(tctx, p, dom2_info_dns->dns.name.string, + dom2_info_dns->dns.dns_domain.string, + dom2_info_dns->dns.sid, &auth_blob)) { + return false; + } + if (!test_setup_trust(tctx, dom2_p, dom1_info_dns->dns.name.string, + dom1_info_dns->dns.dns_domain.string, + dom1_info_dns->dns.sid, &auth_blob)) { + return false; + } + + if (!test_validate_trust(tctx, binding, + dom1_info_dns->dns.name.string, + dom1_info_dns->dns.dns_domain.string, + dom2_info_dns->dns.name.string, + dom2_info_dns->dns.dns_domain.string, test_password)) { + return false; + } + + if (!test_validate_trust(tctx, dom2_binding_string, + dom2_info_dns->dns.name.string, + dom2_info_dns->dns.dns_domain.string, + dom1_info_dns->dns.name.string, + dom1_info_dns->dns.dns_domain.string, test_password)) { + return false; + } + + if (!delete_trusted_domain_by_sid(p, tctx, dom2_info_dns->dns.sid)) { + return false; + } + + if (!delete_trusted_domain_by_sid(dom2_p, tctx, dom1_info_dns->dns.sid)) { + return false; + } + + return true; +} + +/* By default this test creates a trust object in the destination server to a + * dummy domain. If a second server from a different domain is specified on the + * command line a trust is created between those two domains. + * + * Example: + * smbtorture ncacn_np:srv1.dom1.test[print] RPC-LSA-FOREST-TRUST \ + * -U 'dom1\testadm1%12345678' \ + * --option=torture:Forest_Trust_Dom2_Binding=ncacn_np:srv2.dom2.test[print] \ + * --option=torture:Forest_Trust_Dom2_Creds='dom2\testadm2%12345678' + */ + +struct torture_suite *torture_rpc_lsa_forest_trust(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + + suite = torture_suite_create(mem_ctx, "lsa.forest.trust"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "lsa-forest-trust", + &ndr_table_lsarpc); + torture_rpc_tcase_add_test(tcase, "ForestTrust", testcase_ForestTrusts); + + return suite; +} diff --git a/source4/torture/rpc/frsapi.c b/source4/torture/rpc/frsapi.c new file mode 100644 index 0000000..710826a --- /dev/null +++ b/source4/torture/rpc/frsapi.c @@ -0,0 +1,276 @@ +/* + Unix SMB/CIFS implementation. + test suite for rpc frsapi operations + + Copyright (C) Guenther Deschner 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_frsapi_c.h" +#include "param/param.h" + +static bool test_GetDsPollingIntervalW(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t *CurrentInterval, + uint32_t *DsPollingLongInterval, + uint32_t *DsPollingShortInterval) +{ + struct frsapi_GetDsPollingIntervalW r; + + ZERO_STRUCT(r); + + r.out.CurrentInterval = CurrentInterval; + r.out.DsPollingLongInterval = DsPollingLongInterval; + r.out.DsPollingShortInterval = DsPollingShortInterval; + + torture_assert_ntstatus_ok(tctx, + dcerpc_frsapi_GetDsPollingIntervalW_r(b, tctx, &r), + "GetDsPollingIntervalW failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "GetDsPollingIntervalW failed"); + + return true; +} + +static bool test_SetDsPollingIntervalW(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t CurrentInterval, + uint32_t DsPollingLongInterval, + uint32_t DsPollingShortInterval) +{ + struct frsapi_SetDsPollingIntervalW r; + + ZERO_STRUCT(r); + + r.in.CurrentInterval = CurrentInterval; + r.in.DsPollingLongInterval = DsPollingLongInterval; + r.in.DsPollingShortInterval = DsPollingShortInterval; + + torture_assert_ntstatus_ok(tctx, + dcerpc_frsapi_SetDsPollingIntervalW_r(b, tctx, &r), + "SetDsPollingIntervalW failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "SetDsPollingIntervalW failed"); + + return true; +} + +static bool test_DsPollingIntervalW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t i1, i2, i3; + uint32_t k1, k2, k3; + + if (!test_GetDsPollingIntervalW(tctx, b, &i1, &i2, &i3)) { + return false; + } + + if (!test_SetDsPollingIntervalW(tctx, b, i1, i2, i3)) { + return false; + } + + k1 = i1; + k2 = k3 = 0; + + if (!test_SetDsPollingIntervalW(tctx, b, k1, k2, k3)) { + return false; + } + + if (!test_GetDsPollingIntervalW(tctx, b, &k1, &k2, &k3)) { + return false; + } + + if ((i1 != k1) || (i2 != k2) || (i3 != k3)) { + return false; + } + + return true; +} + +static bool test_IsPathReplicated_err(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *path, + uint32_t type, + WERROR werr) +{ + struct frsapi_IsPathReplicated r; + struct GUID guid; + uint32_t replicated, primary, root; + + ZERO_STRUCT(r); + + r.in.path = path; + r.in.replica_set_type = type; + r.out.replicated = &replicated; + r.out.primary = &primary; + r.out.root = &root; + r.out.replica_set_guid = &guid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_frsapi_IsPathReplicated_r(b, tctx, &r), + "IsPathReplicated failed"); + + torture_assert_werr_equal(tctx, r.out.result, werr, + "GetDsPollingIntervalW failed"); + + return true; +} + +static bool _test_IsPathReplicated(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *path, + uint32_t type) +{ + return test_IsPathReplicated_err(tctx, b, path, type, WERR_OK); +} + +static bool test_IsPathReplicated(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + const uint32_t lvls[] = { + FRSAPI_REPLICA_SET_TYPE_0, + FRSAPI_REPLICA_SET_TYPE_DOMAIN, + FRSAPI_REPLICA_SET_TYPE_DFS }; + int i; + bool ret = true; + + if (!test_IsPathReplicated_err(tctx, b, NULL, 0, + WERR_FRS_ERR_INVALID_SERVICE_PARAMETER)) { + ret = false; + } + + for (i=0; i<ARRAY_SIZE(lvls); i++) { + if (!_test_IsPathReplicated(tctx, b, dcerpc_server_name(p), + lvls[i])) { + ret = false; + } + } + + for (i=0; i<ARRAY_SIZE(lvls); i++) { + const char *path = talloc_asprintf(tctx, "\\\\%s\\SYSVOL", + dcerpc_server_name(p)); + if (!_test_IsPathReplicated(tctx, b, path, lvls[i])) { + ret = false; + } + } + + for (i=0; i<ARRAY_SIZE(lvls); i++) { + if (!_test_IsPathReplicated(tctx, b, + "C:\\windows\\sysvol\\domain", + lvls[i])) { + ret = false; + } + } + + return ret; +} + +static bool test_ForceReplication(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct frsapi_ForceReplication r; + + ZERO_STRUCT(r); + + r.in.replica_set_guid = NULL; + r.in.connection_guid = NULL; + r.in.replica_set_name = lpcfg_dnsdomain(tctx->lp_ctx); + r.in.partner_dns_name = dcerpc_server_name(p); + + torture_assert_ntstatus_ok(tctx, + dcerpc_frsapi_ForceReplication_r(b, tctx, &r), + "ForceReplication failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "ForceReplication failed"); + + return true; +} + +static bool test_InfoW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + int i; + + for (i=0; i<10; i++) { + + struct frsapi_InfoW r; + struct frsapi_Info *info; + int d; + DATA_BLOB blob; + + ZERO_STRUCT(r); + + info = talloc_zero(tctx, struct frsapi_Info); + + r.in.length = 0x1000; + r.in.info = r.out.info = info; + + info->length = r.in.length; + info->length2 = r.in.length; + info->level = i; + info->offset = 0x2c; + info->blob_len = 0x2c; + + torture_assert_ntstatus_ok(tctx, + dcerpc_frsapi_InfoW_r(b, tctx, &r), + "InfoW failed"); + + torture_assert_werr_ok(tctx, r.out.result, "InfoW failed"); + + /* display the formatted blob text */ + blob = r.out.info->blob; + for (d = 0; d < blob.length; d++) { + if (blob.data[d]) { + printf("%c", blob.data[d]); + } + } + printf("\n"); + } + + return true; +} + +struct torture_suite *torture_rpc_frsapi(TALLOC_CTX *mem_ctx) +{ + struct torture_rpc_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "frsapi"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "frsapi", + &ndr_table_frsapi); + + torture_rpc_tcase_add_test(tcase, "DsPollingIntervalW", + test_DsPollingIntervalW); + + torture_rpc_tcase_add_test(tcase, "IsPathReplicated", + test_IsPathReplicated); + + torture_rpc_tcase_add_test(tcase, "ForceReplication", + test_ForceReplication); + + torture_rpc_tcase_add_test(tcase, "InfoW", + test_InfoW); + + return suite; +} diff --git a/source4/torture/rpc/fsrvp.c b/source4/torture/rpc/fsrvp.c new file mode 100644 index 0000000..63b03f9 --- /dev/null +++ b/source4/torture/rpc/fsrvp.c @@ -0,0 +1,968 @@ +/* + Unix SMB/CIFS implementation. + + test suite for File Server Remote VSS Protocol operations + + Copyright (C) David Disseldorp 2012-2013 + + 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/>. +*/ + +/* + * Windows Server "8" Beta is very picky in how it accepts FSRVP requests, the + * client must be a member of the same AD domain, ndr64 and signing must be + * negotiated for the DCE/RPC bind. E.g. + * + * smbtorture ncacn_np:LUTZE[/pipe/FssagentRpc,smb2,ndr64,sign] \ + * -U 'DOM\user%pw' rpc.fsrvp + * + * This test suite requires a snapshotable share named FSHARE (see #def below). + */ +#include "includes.h" +#include "lib/param/param.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/resolve/resolve.h" +#include "libcli/util/hresult.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security_descriptor.h" +#include "torture/torture.h" +#include "torture/smb2/proto.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_srvsvc_c.h" +#include "librpc/gen_ndr/ndr_fsrvp_c.h" +#include "lib/cmdline/cmdline.h" + +#define FSHARE "fsrvp_share" +#define FNAME "testfss.dat" + +static bool test_fsrvp_is_path_supported(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fss_IsPathSupported r; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + + ZERO_STRUCT(r); + r.in.ShareName = talloc_asprintf(tctx,"\\\\%s\\%s\\", + dcerpc_server_name(p), + FSHARE); + status = dcerpc_fss_IsPathSupported_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "IsPathSupported failed"); + + torture_assert(tctx, *r.out.SupportedByThisProvider, + "path not supported"); + + torture_comment(tctx, "path %s is supported by fsrvp server %s\n", + r.in.ShareName, *r.out.OwnerMachineName); + + return true; +} + +static bool test_fsrvp_get_version(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fss_GetSupportedVersion r; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + + ZERO_STRUCT(r); + status = dcerpc_fss_GetSupportedVersion_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "GetSupportedVersion failed"); + + torture_comment(tctx, "got MinVersion %u\n", *r.out.MinVersion); + torture_comment(tctx, "got MaxVersion %u\n", *r.out.MaxVersion); + + return true; +} + +static bool test_fsrvp_set_ctx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fss_SetContext r; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + + ZERO_STRUCT(r); + r.in.Context = FSRVP_CTX_BACKUP; + status = dcerpc_fss_SetContext_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "SetContext failed"); + + return true; +} + +enum test_fsrvp_inject { + TEST_FSRVP_TOUT_NONE = 0, + TEST_FSRVP_TOUT_SET_CTX, + TEST_FSRVP_TOUT_START_SET, + TEST_FSRVP_TOUT_ADD_TO_SET, + TEST_FSRVP_TOUT_PREPARE, + TEST_FSRVP_TOUT_COMMIT, + + TEST_FSRVP_STOP_B4_EXPOSE, +}; + +static bool test_fsrvp_sc_create(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *share, + enum test_fsrvp_inject inject, + struct fssagent_share_mapping_1 **sc_map) +{ + struct fss_IsPathSupported r_pathsupport_get; + struct fss_GetSupportedVersion r_version_get; + struct fss_SetContext r_context_set; + struct fss_StartShadowCopySet r_scset_start; + struct fss_AddToShadowCopySet r_scset_add1; + struct fss_AddToShadowCopySet r_scset_add2; + struct fss_PrepareShadowCopySet r_scset_prep; + struct fss_CommitShadowCopySet r_scset_commit; + struct fss_ExposeShadowCopySet r_scset_expose; + struct fss_GetShareMapping r_sharemap_get; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + time_t start_time; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + struct fssagent_share_mapping_1 *map = NULL; + int sleep_time; + + /* + * PrepareShadowCopySet & CommitShadowCopySet often exceed the default + * 60 second dcerpc request timeout against Windows Server "8" Beta. + */ + dcerpc_binding_handle_set_timeout(b, 240); + + ZERO_STRUCT(r_pathsupport_get); + r_pathsupport_get.in.ShareName = share; + status = dcerpc_fss_IsPathSupported_r(b, tmp_ctx, &r_pathsupport_get); + torture_assert_ntstatus_ok(tctx, status, + "IsPathSupported failed"); + torture_assert_int_equal(tctx, r_pathsupport_get.out.result, 0, + "failed IsPathSupported response"); + torture_assert(tctx, r_pathsupport_get.out.SupportedByThisProvider, + "path not supported"); + + ZERO_STRUCT(r_version_get); + status = dcerpc_fss_GetSupportedVersion_r(b, tmp_ctx, &r_version_get); + torture_assert_ntstatus_ok(tctx, status, + "GetSupportedVersion failed"); + torture_assert_int_equal(tctx, r_version_get.out.result, 0, + "failed GetSupportedVersion response"); + + ZERO_STRUCT(r_context_set); + r_context_set.in.Context = FSRVP_CTX_BACKUP; + status = dcerpc_fss_SetContext_r(b, tmp_ctx, &r_context_set); + torture_assert_ntstatus_ok(tctx, status, "SetContext failed"); + torture_assert_int_equal(tctx, r_context_set.out.result, 0, + "failed SetContext response"); + + if (inject == TEST_FSRVP_TOUT_SET_CTX) { + sleep_time = lpcfg_parm_int(tctx->lp_ctx, NULL, "fss", + "sequence timeout", 180); + torture_comment(tctx, "sleeping for %d\n", sleep_time); + smb_msleep((sleep_time * 1000) + 500); + } + + ZERO_STRUCT(r_scset_start); + r_scset_start.in.ClientShadowCopySetId = GUID_random(); + status = dcerpc_fss_StartShadowCopySet_r(b, tmp_ctx, &r_scset_start); + torture_assert_ntstatus_ok(tctx, status, + "StartShadowCopySet failed"); + if (inject == TEST_FSRVP_TOUT_SET_CTX) { + /* expect error due to message sequence timeout after set_ctx */ + torture_assert_int_equal(tctx, r_scset_start.out.result, + FSRVP_E_BAD_STATE, + "StartShadowCopySet timeout response"); + goto done; + } + torture_assert_int_equal(tctx, r_scset_start.out.result, 0, + "failed StartShadowCopySet response"); + torture_comment(tctx, "%s: shadow-copy set created\n", + GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId)); + + if (inject == TEST_FSRVP_TOUT_START_SET) { + sleep_time = lpcfg_parm_int(tctx->lp_ctx, NULL, "fss", + "sequence timeout", 180); + torture_comment(tctx, "sleeping for %d\n", sleep_time); + smb_msleep((sleep_time * 1000) + 500); + } + + ZERO_STRUCT(r_scset_add1); + r_scset_add1.in.ClientShadowCopyId = GUID_random(); + r_scset_add1.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_scset_add1.in.ShareName = share; + status = dcerpc_fss_AddToShadowCopySet_r(b, tmp_ctx, &r_scset_add1); + torture_assert_ntstatus_ok(tctx, status, + "AddToShadowCopySet failed"); + if (inject == TEST_FSRVP_TOUT_START_SET) { + torture_assert_int_equal(tctx, r_scset_add1.out.result, + HRES_ERROR_V(HRES_E_INVALIDARG), + "AddToShadowCopySet timeout response"); + goto done; + } + torture_assert_int_equal(tctx, r_scset_add1.out.result, 0, + "failed AddToShadowCopySet response"); + torture_comment(tctx, "%s(%s): %s added to shadow-copy set\n", + GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId), + GUID_string(tmp_ctx, r_scset_add1.out.pShadowCopyId), + r_scset_add1.in.ShareName); + + /* attempts to add the same share twice should fail */ + ZERO_STRUCT(r_scset_add2); + r_scset_add2.in.ClientShadowCopyId = GUID_random(); + r_scset_add2.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_scset_add2.in.ShareName = share; + status = dcerpc_fss_AddToShadowCopySet_r(b, tmp_ctx, &r_scset_add2); + torture_assert_ntstatus_ok(tctx, status, + "AddToShadowCopySet failed"); + torture_assert_int_equal(tctx, r_scset_add2.out.result, + FSRVP_E_OBJECT_ALREADY_EXISTS, + "failed AddToShadowCopySet response"); + + if (inject == TEST_FSRVP_TOUT_ADD_TO_SET) { + sleep_time = lpcfg_parm_int(tctx->lp_ctx, NULL, "fss", + "sequence timeout", 1800); + torture_comment(tctx, "sleeping for %d\n", sleep_time); + smb_msleep((sleep_time * 1000) + 500); + } + + start_time = time_mono(NULL); + ZERO_STRUCT(r_scset_prep); + r_scset_prep.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; +// r_scset_prep.in.TimeOutInMilliseconds = (1800 * 1000); /* win8 */ + r_scset_prep.in.TimeOutInMilliseconds = (240 * 1000); + status = dcerpc_fss_PrepareShadowCopySet_r(b, tmp_ctx, &r_scset_prep); + torture_assert_ntstatus_ok(tctx, status, + "PrepareShadowCopySet failed"); + if (inject == TEST_FSRVP_TOUT_ADD_TO_SET) { + torture_assert_int_equal(tctx, r_scset_prep.out.result, + HRES_ERROR_V(HRES_E_INVALIDARG), + "PrepareShadowCopySet tout response"); + goto done; + } + torture_assert_int_equal(tctx, r_scset_prep.out.result, 0, + "failed PrepareShadowCopySet response"); + torture_comment(tctx, "%s: prepare completed in %llu secs\n", + GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId), + (unsigned long long)(time_mono(NULL) - start_time)); + + if (inject == TEST_FSRVP_TOUT_PREPARE) { + sleep_time = lpcfg_parm_int(tctx->lp_ctx, NULL, "fss", + "sequence timeout", 1800); + torture_comment(tctx, "sleeping for %d\n", sleep_time); + smb_msleep((sleep_time * 1000) + 500); + } + + start_time = time_mono(NULL); + ZERO_STRUCT(r_scset_commit); + r_scset_commit.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_scset_commit.in.TimeOutInMilliseconds = (180 * 1000); /* win8 */ + status = dcerpc_fss_CommitShadowCopySet_r(b, tmp_ctx, &r_scset_commit); + torture_assert_ntstatus_ok(tctx, status, + "CommitShadowCopySet failed"); + if (inject == TEST_FSRVP_TOUT_PREPARE) { + torture_assert_int_equal(tctx, r_scset_commit.out.result, + HRES_ERROR_V(HRES_E_INVALIDARG), + "CommitShadowCopySet tout response"); + goto done; + } + torture_assert_int_equal(tctx, r_scset_commit.out.result, 0, + "failed CommitShadowCopySet response"); + torture_comment(tctx, "%s: commit completed in %llu secs\n", + GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId), + (unsigned long long)(time_mono(NULL) - start_time)); + + if (inject == TEST_FSRVP_TOUT_COMMIT) { + sleep_time = lpcfg_parm_int(tctx->lp_ctx, NULL, "fss", + "sequence timeout", 180); + torture_comment(tctx, "sleeping for %d\n", sleep_time); + smb_msleep((sleep_time * 1000) + 500); + } else if (inject == TEST_FSRVP_STOP_B4_EXPOSE) { + /* return partial snapshot information */ + map = talloc_zero(tctx, struct fssagent_share_mapping_1); + map->ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + map->ShadowCopyId = *r_scset_add1.out.pShadowCopyId; + goto done; + } + + start_time = time_mono(NULL); + ZERO_STRUCT(r_scset_expose); + r_scset_expose.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_scset_expose.in.TimeOutInMilliseconds = (120 * 1000); /* win8 */ + status = dcerpc_fss_ExposeShadowCopySet_r(b, tmp_ctx, &r_scset_expose); + torture_assert_ntstatus_ok(tctx, status, + "ExposeShadowCopySet failed"); + if (inject == TEST_FSRVP_TOUT_COMMIT) { + torture_assert_int_equal(tctx, r_scset_expose.out.result, + HRES_ERROR_V(HRES_E_INVALIDARG), + "ExposeShadowCopySet tout response"); + goto done; + } + torture_assert_int_equal(tctx, r_scset_expose.out.result, 0, + "failed ExposeShadowCopySet response"); + torture_comment(tctx, "%s: expose completed in %llu secs\n", + GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId), + (unsigned long long)(time_mono(NULL) - start_time)); + + ZERO_STRUCT(r_sharemap_get); + r_sharemap_get.in.ShadowCopyId = *r_scset_add1.out.pShadowCopyId; + r_sharemap_get.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_sharemap_get.in.ShareName = r_scset_add1.in.ShareName; + r_sharemap_get.in.Level = 1; + status = dcerpc_fss_GetShareMapping_r(b, tmp_ctx, &r_sharemap_get); + torture_assert_ntstatus_ok(tctx, status, "GetShareMapping failed"); + torture_assert_int_equal(tctx, r_sharemap_get.out.result, 0, + "failed GetShareMapping response"); + torture_comment(tctx, "%s(%s): %s is a snapshot of %s at %s\n", + GUID_string(tmp_ctx, &r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopySetId), + GUID_string(tmp_ctx, &r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopyId), + r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopyShareName, + r_sharemap_get.out.ShareMapping->ShareMapping1->ShareNameUNC, + nt_time_string(tmp_ctx, r_sharemap_get.out.ShareMapping->ShareMapping1->tstamp)); + + map = talloc_zero(tctx, struct fssagent_share_mapping_1); + map->ShadowCopySetId = r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopySetId; + map->ShadowCopyId = r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopyId; + map->ShadowCopyShareName + = talloc_strdup(tctx, r_sharemap_get.out.ShareMapping->ShareMapping1->ShadowCopyShareName); + map->ShareNameUNC + = talloc_strdup(tctx, r_sharemap_get.out.ShareMapping->ShareMapping1->ShareNameUNC); + map->tstamp = r_sharemap_get.out.ShareMapping->ShareMapping1->tstamp; + + torture_assert(tctx, !GUID_compare(&r_sharemap_get.in.ShadowCopySetId, + &map->ShadowCopySetId), + "sc_set GUID mismatch in GetShareMapping"); + torture_assert(tctx, !GUID_compare(&r_sharemap_get.in.ShadowCopyId, + &map->ShadowCopyId), + "sc GUID mismatch in GetShareMapping"); + +done: + talloc_free(tmp_ctx); + *sc_map = map; + + return true; +} + +static bool test_fsrvp_sc_delete(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct fssagent_share_mapping_1 *sc_map) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct fss_DeleteShareMapping r_sharemap_del; + NTSTATUS status; + + ZERO_STRUCT(r_sharemap_del); + r_sharemap_del.in.ShadowCopySetId = sc_map->ShadowCopySetId; + r_sharemap_del.in.ShadowCopyId = sc_map->ShadowCopyId; + r_sharemap_del.in.ShareName = sc_map->ShareNameUNC; + status = dcerpc_fss_DeleteShareMapping_r(b, tctx, &r_sharemap_del); + torture_assert_ntstatus_ok(tctx, status, "DeleteShareMapping failed"); + torture_assert_int_equal(tctx, r_sharemap_del.out.result, 0, + "failed DeleteShareMapping response"); + + return true; +} + +static bool test_fsrvp_sc_create_simple(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fssagent_share_mapping_1 *sc_map; + /* no trailing backslash - should work. See note in cmd_fss.c */ + char *share_unc = talloc_asprintf(tctx, "\\\\%s\\%s", + dcerpc_server_name(p), FSHARE); + + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, TEST_FSRVP_TOUT_NONE, &sc_map), + "sc create"); + + torture_assert(tctx, test_fsrvp_sc_delete(tctx, p, sc_map), "sc del"); + + return true; +} + +static bool test_fsrvp_sc_set_abort(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + char *share_unc = talloc_asprintf(tctx, "\\\\%s\\%s\\", + dcerpc_server_name(p), FSHARE); + struct dcerpc_binding_handle *b = p->binding_handle; + struct fss_IsPathSupported r_pathsupport_get; + struct fss_GetSupportedVersion r_version_get; + struct fss_SetContext r_context_set; + struct fss_StartShadowCopySet r_scset_start; + struct fss_AbortShadowCopySet r_scset_abort; + struct fss_AddToShadowCopySet r_scset_add; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + + ZERO_STRUCT(r_pathsupport_get); + r_pathsupport_get.in.ShareName = share_unc; + status = dcerpc_fss_IsPathSupported_r(b, tmp_ctx, &r_pathsupport_get); + torture_assert_ntstatus_ok(tctx, status, + "IsPathSupported failed"); + torture_assert(tctx, r_pathsupport_get.out.SupportedByThisProvider, + "path not supported"); + + ZERO_STRUCT(r_version_get); + status = dcerpc_fss_GetSupportedVersion_r(b, tmp_ctx, &r_version_get); + torture_assert_ntstatus_ok(tctx, status, + "GetSupportedVersion failed"); + + ZERO_STRUCT(r_context_set); + r_context_set.in.Context = FSRVP_CTX_BACKUP; + status = dcerpc_fss_SetContext_r(b, tmp_ctx, &r_context_set); + torture_assert_ntstatus_ok(tctx, status, "SetContext failed"); + + ZERO_STRUCT(r_scset_start); + r_scset_start.in.ClientShadowCopySetId = GUID_random(); + status = dcerpc_fss_StartShadowCopySet_r(b, tmp_ctx, &r_scset_start); + torture_assert_ntstatus_ok(tctx, status, + "StartShadowCopySet failed"); + + ZERO_STRUCT(r_scset_abort); + r_scset_abort.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + status = dcerpc_fss_AbortShadowCopySet_r(b, tmp_ctx, &r_scset_abort); + torture_assert_ntstatus_ok(tctx, status, + "AbortShadowCopySet failed"); + + ZERO_STRUCT(r_scset_add); + r_scset_add.in.ClientShadowCopyId = GUID_random(); + r_scset_add.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId; + r_scset_add.in.ShareName = share_unc; + status = dcerpc_fss_AddToShadowCopySet_r(b, tmp_ctx, &r_scset_add); + torture_assert_ntstatus_ok(tctx, status, "AddToShadowCopySet failed " + "following abort"); + /* + * XXX Windows 8 server beta returns FSRVP_E_BAD_STATE here rather than + * FSRVP_E_BAD_ID / HRES_E_INVALIDARG. + */ + torture_assert(tctx, (r_scset_add.out.result != 0), + "incorrect AddToShadowCopySet response following abort"); + + talloc_free(tmp_ctx); + return true; +} + +static bool test_fsrvp_bad_id(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fssagent_share_mapping_1 *sc_map; + struct dcerpc_binding_handle *b = p->binding_handle; + struct fss_DeleteShareMapping r_sharemap_del; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + char *share_unc = talloc_asprintf(tmp_ctx, "\\\\%s\\%s\\", + dcerpc_server_name(p), FSHARE); + + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, TEST_FSRVP_TOUT_NONE, &sc_map), + "sc create"); + + ZERO_STRUCT(r_sharemap_del); + r_sharemap_del.in.ShadowCopySetId = sc_map->ShadowCopySetId; + r_sharemap_del.in.ShadowCopySetId.time_low++; /* bogus */ + r_sharemap_del.in.ShadowCopyId = sc_map->ShadowCopyId; + r_sharemap_del.in.ShareName = sc_map->ShareNameUNC; + status = dcerpc_fss_DeleteShareMapping_r(b, tmp_ctx, &r_sharemap_del); + torture_assert_ntstatus_ok(tctx, status, + "DeleteShareMapping failed"); + torture_assert_int_equal(tctx, r_sharemap_del.out.result, + FSRVP_E_OBJECT_NOT_FOUND, + "incorrect DeleteShareMapping response"); + + r_sharemap_del.in.ShadowCopySetId = sc_map->ShadowCopySetId; + r_sharemap_del.in.ShadowCopyId.time_mid++; /* bogus */ + status = dcerpc_fss_DeleteShareMapping_r(b, tmp_ctx, &r_sharemap_del); + torture_assert_ntstatus_ok(tctx, status, + "DeleteShareMapping failed"); + torture_assert_int_equal(tctx, r_sharemap_del.out.result, + HRES_ERROR_V(HRES_E_INVALIDARG), + "incorrect DeleteShareMapping response"); + + torture_assert(tctx, test_fsrvp_sc_delete(tctx, p, sc_map), "sc del"); + + talloc_free(sc_map); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_fsrvp_sc_share_io(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fssagent_share_mapping_1 *sc_map; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + char *share_unc = talloc_asprintf(tmp_ctx, "\\\\%s\\%s", + dcerpc_server_name(p), FSHARE); + struct smb2_tree *tree_base; + struct smb2_tree *tree_snap; + struct smbcli_options options; + struct smb2_handle base_fh; + struct smb2_read r; + struct smb2_create io; + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(tmp_ctx, + dcerpc_server_name(p), + lpcfg_smb_ports(tctx->lp_ctx), + FSHARE, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree_base, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, + "Failed to connect to SMB2 share"); + + smb2_util_unlink(tree_base, FNAME); + status = torture_smb2_testfile(tree_base, FNAME, &base_fh); + torture_assert_ntstatus_ok(tctx, status, "base write open"); + + status = smb2_util_write(tree_base, base_fh, "pre-snap", 0, + sizeof("pre-snap")); + torture_assert_ntstatus_ok(tctx, status, "src write"); + + + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, TEST_FSRVP_TOUT_NONE, &sc_map), + "sc create"); + + status = smb2_util_write(tree_base, base_fh, "post-snap", 0, + sizeof("post-snap")); + torture_assert_ntstatus_ok(tctx, status, "base write"); + + /* connect to snapshot share and verify pre-snapshot data */ + status = smb2_connect(tmp_ctx, + dcerpc_server_name(p), + lpcfg_smb_ports(tctx->lp_ctx), + sc_map->ShadowCopyShareName, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree_snap, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, + "Failed to connect to SMB2 shadow-copy share"); + /* Windows server 8 allows RW open to succeed here for a ro snapshot */ + ZERO_STRUCT(io); + io.in.desired_access = SEC_RIGHTS_FILE_READ; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OPEN_IF; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_DELETE| + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = FNAME; + status = smb2_create(tree_snap, tmp_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "snap read open"); + + ZERO_STRUCT(r); + r.in.file.handle = io.out.file.handle; + r.in.length = sizeof("pre-snap"); + status = smb2_read(tree_snap, tmp_ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "read"); + torture_assert_u64_equal(tctx, r.out.data.length, r.in.length, + "read data len mismatch"); + torture_assert_str_equal(tctx, (char *)r.out.data.data, "pre-snap", + "bad snapshot data"); + + torture_assert(tctx, test_fsrvp_sc_delete(tctx, p, sc_map), "sc del"); + + talloc_free(sc_map); + talloc_free(tmp_ctx); + + return true; +} + +static bool test_fsrvp_enum_snaps(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + struct smb2_handle fh, + int *_count) +{ + struct smb2_ioctl io; + NTSTATUS status; + + ZERO_STRUCT(io); + io.level = RAW_IOCTL_SMB2; + io.in.file.handle = fh; + io.in.function = FSCTL_SRV_ENUM_SNAPS; + io.in.max_output_response = 16; + io.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL; + + status = smb2_ioctl(tree, mem_ctx, &io); + torture_assert_ntstatus_ok(tctx, status, "enum ioctl"); + + *_count = IVAL(io.out.out.data, 0); + + /* with max_output_response=16, no labels should be sent */ + torture_assert_int_equal(tctx, IVAL(io.out.out.data, 4), 0, + "enum snaps labels"); + + /* TODO with 0 snaps, needed_data_count should be 0? */ + if (*_count != 0) { + torture_assert(tctx, IVAL(io.out.out.data, 8) != 0, + "enum snaps needed non-zero"); + } + + return true; +} + +static bool test_fsrvp_enum_created(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct fssagent_share_mapping_1 *sc_map; + NTSTATUS status; + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + char *share_unc = talloc_asprintf(tmp_ctx, "\\\\%s\\%s\\", + dcerpc_server_name(p), FSHARE); + struct smb2_tree *tree_base; + struct smbcli_options options; + struct smb2_handle base_fh; + int count; + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(tmp_ctx, + dcerpc_server_name(p), + lpcfg_smb_ports(tctx->lp_ctx), + FSHARE, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree_base, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, + "Failed to connect to SMB2 share"); + + smb2_util_unlink(tree_base, FNAME); + status = torture_smb2_testfile(tree_base, FNAME, &base_fh); + torture_assert_ntstatus_ok(tctx, status, "base write open"); + + status = smb2_util_write(tree_base, base_fh, "pre-snap", 0, + sizeof("pre-snap")); + torture_assert_ntstatus_ok(tctx, status, "src write"); + + torture_assert(tctx, + test_fsrvp_enum_snaps(tctx, tmp_ctx, tree_base, base_fh, + &count), + "count"); + torture_assert_int_equal(tctx, count, 0, "num snaps"); + + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, TEST_FSRVP_TOUT_NONE, &sc_map), + "sc create"); + talloc_free(sc_map); + + torture_assert(tctx, + test_fsrvp_enum_snaps(tctx, tmp_ctx, tree_base, base_fh, + &count), + "count"); + /* + * Snapshots created via FSRVP on Windows Server 2012 are not added to + * the previous versions list, so it will fail here... + */ + torture_assert_int_equal(tctx, count, 1, "num snaps"); + + smb_msleep(1100); /* @GMT tokens have a 1 second resolution */ + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, TEST_FSRVP_TOUT_NONE, &sc_map), + "sc create"); + talloc_free(sc_map); + + torture_assert(tctx, + test_fsrvp_enum_snaps(tctx, tmp_ctx, tree_base, base_fh, + &count), + "count"); + torture_assert_int_equal(tctx, count, 2, "num snaps"); + + talloc_free(tmp_ctx); + + return true; +} + +static bool test_fsrvp_seq_timeout(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + int i; + struct fssagent_share_mapping_1 *sc_map; + char *share_unc = talloc_asprintf(tctx, "\\\\%s\\%s", + dcerpc_server_name(p), FSHARE); + + for (i = TEST_FSRVP_TOUT_NONE; i <= TEST_FSRVP_TOUT_COMMIT; i++) { + torture_assert(tctx, test_fsrvp_sc_create(tctx, p, share_unc, + i, &sc_map), + "sc create"); + + /* only need to delete if create process didn't timeout */ + if (i == TEST_FSRVP_TOUT_NONE) { + torture_assert(tctx, test_fsrvp_sc_delete(tctx, p, sc_map), + "sc del"); + } + } + + return true; +} + +static bool test_fsrvp_share_sd(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct dcerpc_pipe *srvsvc_p; + struct srvsvc_NetShareGetInfo q; + struct srvsvc_NetShareSetInfo s; + struct srvsvc_NetShareInfo502 *info502; + struct fssagent_share_mapping_1 *sc_map; + struct fss_ExposeShadowCopySet r_scset_expose; + struct fss_GetShareMapping r_sharemap_get; + struct security_descriptor *sd_old; + struct security_descriptor *sd_base; + struct security_descriptor *sd_snap; + struct security_ace *ace; + int i; + int aces_found; + char *share_unc = talloc_asprintf(tctx, "\\\\%s\\%s", + dcerpc_server_name(p), FSHARE); + ZERO_STRUCT(q); + q.in.server_unc = dcerpc_server_name(p); + q.in.share_name = FSHARE; + q.in.level = 502; + + status = torture_rpc_connection(tctx, &srvsvc_p, &ndr_table_srvsvc); + torture_assert_ntstatus_ok(tctx, status, "srvsvc rpc conn failed"); + + /* ensure srvsvc out pointers are allocated during unmarshalling */ + srvsvc_p->conn->flags |= DCERPC_NDR_REF_ALLOC; + + /* obtain the existing DACL for the base share */ + status = dcerpc_srvsvc_NetShareGetInfo_r(srvsvc_p->binding_handle, + tctx, &q); + torture_assert_ntstatus_ok(tctx, status, "NetShareGetInfo failed"); + torture_assert_werr_ok(tctx, q.out.result, "NetShareGetInfo failed"); + + info502 = q.out.info->info502; + + /* back up the existing share SD, so it can be restored on completion */ + sd_old = info502->sd_buf.sd; + sd_base = security_descriptor_copy(tctx, info502->sd_buf.sd); + torture_assert(tctx, sd_base != NULL, "sd dup"); + torture_assert(tctx, sd_base->dacl != NULL, "no existing share DACL"); + + /* the Builtin_X_Operators placeholder ACEs need to be unique */ + for (i = 0; i < sd_base->dacl->num_aces; i++) { + ace = &sd_base->dacl->aces[i]; + if (dom_sid_equal(&ace->trustee, + &global_sid_Builtin_Backup_Operators) + || dom_sid_equal(&ace->trustee, + &global_sid_Builtin_Print_Operators)) { + torture_skip(tctx, "placeholder ACE already exists\n"); + } + } + + /* add Backup_Operators placeholder ACE and set base share DACL */ + ace = talloc_zero(tctx, struct security_ace); + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->access_mask = SEC_STD_SYNCHRONIZE; + ace->trustee = global_sid_Builtin_Backup_Operators; + + status = security_descriptor_dacl_add(sd_base, ace); + torture_assert_ntstatus_ok(tctx, status, + "failed to add placeholder ACE to DACL"); + + info502->sd_buf.sd = sd_base; + info502->sd_buf.sd_size = ndr_size_security_descriptor(sd_base, 0); + + ZERO_STRUCT(s); + s.in.server_unc = dcerpc_server_name(p); + s.in.share_name = FSHARE; + s.in.level = 502; + s.in.info = q.out.info; + + status = dcerpc_srvsvc_NetShareSetInfo_r(srvsvc_p->binding_handle, + tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "NetShareSetInfo failed"); + torture_assert_werr_ok(tctx, s.out.result, "NetShareSetInfo failed"); + + /* create a snapshot, but don't expose yet */ + torture_assert(tctx, + test_fsrvp_sc_create(tctx, p, share_unc, + TEST_FSRVP_STOP_B4_EXPOSE, &sc_map), + "sc create"); + + /* + * Add another unique placeholder ACE. + * By changing the share DACL between snapshot creation and exposure we + * can determine at which point the server clones the base share DACL. + */ + ace = talloc_zero(tctx, struct security_ace); + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->access_mask = SEC_STD_SYNCHRONIZE; + ace->trustee = global_sid_Builtin_Print_Operators; + + status = security_descriptor_dacl_add(sd_base, ace); + torture_assert_ntstatus_ok(tctx, status, + "failed to add placeholder ACE to DACL"); + + info502->sd_buf.sd = sd_base; + info502->sd_buf.sd_size = ndr_size_security_descriptor(sd_base, 0); + + ZERO_STRUCT(s); + s.in.server_unc = dcerpc_server_name(p); + s.in.share_name = FSHARE; + s.in.level = 502; + s.in.info = q.out.info; + + status = dcerpc_srvsvc_NetShareSetInfo_r(srvsvc_p->binding_handle, + tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "NetShareSetInfo failed"); + torture_assert_werr_ok(tctx, s.out.result, "NetShareSetInfo failed"); + + /* expose the snapshot share and get the new share details */ + ZERO_STRUCT(r_scset_expose); + r_scset_expose.in.ShadowCopySetId = sc_map->ShadowCopySetId; + r_scset_expose.in.TimeOutInMilliseconds = (120 * 1000); /* win8 */ + status = dcerpc_fss_ExposeShadowCopySet_r(p->binding_handle, tctx, + &r_scset_expose); + torture_assert_ntstatus_ok(tctx, status, + "ExposeShadowCopySet failed"); + torture_assert_int_equal(tctx, r_scset_expose.out.result, 0, + "failed ExposeShadowCopySet response"); + + ZERO_STRUCT(r_sharemap_get); + r_sharemap_get.in.ShadowCopyId = sc_map->ShadowCopyId; + r_sharemap_get.in.ShadowCopySetId = sc_map->ShadowCopySetId; + r_sharemap_get.in.ShareName = share_unc; + r_sharemap_get.in.Level = 1; + status = dcerpc_fss_GetShareMapping_r(p->binding_handle, tctx, + &r_sharemap_get); + torture_assert_ntstatus_ok(tctx, status, "GetShareMapping failed"); + torture_assert_int_equal(tctx, r_sharemap_get.out.result, 0, + "failed GetShareMapping response"); + talloc_free(sc_map); + sc_map = r_sharemap_get.out.ShareMapping->ShareMapping1; + + /* restore the original base share ACL */ + info502->sd_buf.sd = sd_old; + info502->sd_buf.sd_size = ndr_size_security_descriptor(sd_old, 0); + status = dcerpc_srvsvc_NetShareSetInfo_r(srvsvc_p->binding_handle, + tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "NetShareSetInfo failed"); + torture_assert_werr_ok(tctx, s.out.result, "NetShareSetInfo failed"); + + /* check for placeholder ACEs in the snapshot share DACL */ + ZERO_STRUCT(q); + q.in.server_unc = dcerpc_server_name(p); + q.in.share_name = sc_map->ShadowCopyShareName; + q.in.level = 502; + status = dcerpc_srvsvc_NetShareGetInfo_r(srvsvc_p->binding_handle, + tctx, &q); + torture_assert_ntstatus_ok(tctx, status, "NetShareGetInfo failed"); + torture_assert_werr_ok(tctx, q.out.result, "NetShareGetInfo failed"); + info502 = q.out.info->info502; + + sd_snap = info502->sd_buf.sd; + torture_assert(tctx, sd_snap != NULL, "sd"); + torture_assert(tctx, sd_snap->dacl != NULL, "no snap share DACL"); + + aces_found = 0; + for (i = 0; i < sd_snap->dacl->num_aces; i++) { + ace = &sd_snap->dacl->aces[i]; + if (dom_sid_equal(&ace->trustee, + &global_sid_Builtin_Backup_Operators)) { + torture_comment(tctx, + "found share ACE added before snapshot\n"); + aces_found++; + } else if (dom_sid_equal(&ace->trustee, + &global_sid_Builtin_Print_Operators)) { + torture_comment(tctx, + "found share ACE added after snapshot\n"); + aces_found++; + } + } + /* + * Expect snapshot share to match the base share DACL at the time of + * exposure, not at the time of snapshot creation. This is in line with + * Windows Server 2012 behaviour. + */ + torture_assert_int_equal(tctx, aces_found, 2, + "placeholder ACE missing from snap share DACL"); + + torture_assert(tctx, test_fsrvp_sc_delete(tctx, p, sc_map), "sc del"); + + return true; +} + +static bool fsrvp_rpc_setup(struct torture_context *tctx, void **data) +{ + NTSTATUS status; + struct torture_rpc_tcase *tcase = talloc_get_type( + tctx->active_tcase, struct torture_rpc_tcase); + struct torture_rpc_tcase_data *tcase_data; + + *data = tcase_data = talloc_zero(tctx, struct torture_rpc_tcase_data); + tcase_data->credentials = samba_cmdline_get_creds(); + + status = torture_rpc_connection(tctx, + &(tcase_data->pipe), + tcase->table); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + /* XXX required, otherwise ndr out ptrs are not allocated */ + tcase_data->pipe->conn->flags |= DCERPC_NDR_REF_ALLOC; + + return true; +} + +/* + testing of FSRVP (FSS agent) +*/ +struct torture_suite *torture_rpc_fsrvp(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "fsrvp"); + + struct torture_rpc_tcase *tcase + = torture_suite_add_rpc_iface_tcase(suite, "fsrvp", + &ndr_table_FileServerVssAgent); + /* override torture_rpc_setup() to set DCERPC_NDR_REF_ALLOC */ + tcase->tcase.setup = fsrvp_rpc_setup; + + torture_rpc_tcase_add_test(tcase, "share_sd", + test_fsrvp_share_sd); + torture_rpc_tcase_add_test(tcase, "enum_created", + test_fsrvp_enum_created); + torture_rpc_tcase_add_test(tcase, "sc_share_io", + test_fsrvp_sc_share_io); + torture_rpc_tcase_add_test(tcase, "bad_id", + test_fsrvp_bad_id); + torture_rpc_tcase_add_test(tcase, "sc_set_abort", + test_fsrvp_sc_set_abort); + torture_rpc_tcase_add_test(tcase, "create_simple", + test_fsrvp_sc_create_simple); + torture_rpc_tcase_add_test(tcase, "set_ctx", + test_fsrvp_set_ctx); + torture_rpc_tcase_add_test(tcase, "get_version", + test_fsrvp_get_version); + torture_rpc_tcase_add_test(tcase, "is_path_supported", + test_fsrvp_is_path_supported); + torture_rpc_tcase_add_test(tcase, "seq_timeout", + test_fsrvp_seq_timeout); + + return suite; +} diff --git a/source4/torture/rpc/handles.c b/source4/torture/rpc/handles.c new file mode 100644 index 0000000..7c108e5 --- /dev/null +++ b/source4/torture/rpc/handles.c @@ -0,0 +1,622 @@ +/* + Unix SMB/CIFS implementation. + + test suite for behaviour of rpc policy handles + + Copyright (C) Andrew Tridgell 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_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_drsuapi_c.h" +#include "torture/rpc/torture_rpc.h" + +/* + this tests the use of policy handles between connections +*/ + +static bool test_handles_lsa(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2; + struct dcerpc_binding_handle *b1, *b2; + struct policy_handle handle; + struct policy_handle handle2; + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy r; + struct lsa_Close c; + uint16_t system_name = '\\'; + TALLOC_CTX *mem_ctx = talloc_new(torture); + + torture_comment(torture, "RPC-HANDLE-LSARPC\n"); + + status = torture_rpc_connection(torture, &p1, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe1"); + b1 = p1->binding_handle; + + status = torture_rpc_connection(torture, &p2, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe1"); + b2 = p2->binding_handle; + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = &system_name; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_OpenPolicy_r(b1, mem_ctx, &r), + "OpenPolicy failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(torture, "lsa_OpenPolicy not supported - skipping\n"); + talloc_free(mem_ctx); + return true; + } + + c.in.handle = &handle; + c.out.handle = &handle2; + + status = dcerpc_lsa_Close_r(b2, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p2"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_Close_r(b1, mem_ctx, &c), + "Close failed"); + torture_assert_ntstatus_ok(torture, c.out.result, "closing policy handle on p1"); + + status = dcerpc_lsa_Close_r(b1, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p1 again"); + + talloc_free(mem_ctx); + + return true; +} + +static bool test_handles_lsa_shared(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2, *p3, *p4, *p5; + struct dcerpc_binding_handle *b1, *b2, *b3, *b4; + struct policy_handle handle; + struct policy_handle handle2; + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy r; + struct lsa_Close c; + struct lsa_QuerySecurity qsec; + struct sec_desc_buf *sdbuf = NULL; + uint16_t system_name = '\\'; + TALLOC_CTX *mem_ctx = talloc_new(torture); + enum dcerpc_transport_t transport; + uint32_t assoc_group_id; + + torture_comment(torture, "RPC-HANDLE-LSARPC-SHARED\n"); + + torture_comment(torture, "connect lsa pipe1\n"); + status = torture_rpc_connection(torture, &p1, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe1"); + b1 = p1->binding_handle; + + transport = p1->conn->transport.transport; + assoc_group_id = dcerpc_binding_get_assoc_group_id(p1->binding); + + torture_comment(torture, "use assoc_group_id[0x%08X] for new connections\n", assoc_group_id); + + torture_comment(torture, "connect lsa pipe2\n"); + status = torture_rpc_connection_transport(torture, &p2, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe2"); + b2 = p2->binding_handle; + + torture_comment(torture, "got assoc_group_id[0x%08X] for p2\n", + dcerpc_binding_get_assoc_group_id(p2->binding)); + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = &system_name; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_comment(torture, "open lsa policy handle\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_OpenPolicy_r(b1, mem_ctx, &r), + "OpenPolicy failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(torture, "lsa_OpenPolicy not supported - skipping\n"); + talloc_free(mem_ctx); + return true; + } + + /* + * connect p3 after the policy handle is opened + */ + torture_comment(torture, "connect lsa pipe3 after the policy handle is opened\n"); + status = torture_rpc_connection_transport(torture, &p3, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe3"); + b3 = p3->binding_handle; + + qsec.in.handle = &handle; + qsec.in.sec_info = 0; + qsec.out.sdbuf = &sdbuf; + c.in.handle = &handle; + c.out.handle = &handle2; + + /* + * use policy handle on all 3 connections + */ + torture_comment(torture, "use the policy handle on p1,p2,p3\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b1, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "use policy handle on p1"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b2, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "use policy handle on p2"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b3, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "use policy handle on p3"); + + /* + * close policy handle on connection 2 and the others get a fault + */ + torture_comment(torture, "close the policy handle on p2 others get a fault\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_Close_r(b2, mem_ctx, &c), + "Close failed"); + torture_assert_ntstatus_equal(torture, c.out.result, NT_STATUS_OK, + "closing policy handle on p2"); + + status = dcerpc_lsa_Close_r(b1, mem_ctx, &c); + + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p1 again"); + + status = dcerpc_lsa_Close_r(b3, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p3"); + + status = dcerpc_lsa_Close_r(b2, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p2 again"); + + /* + * open a new policy handle on p3 + */ + torture_comment(torture, "open a new policy handle on p3\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_OpenPolicy_r(b3, mem_ctx, &r), + "OpenPolicy failed"); + torture_assert_ntstatus_equal(torture, r.out.result, NT_STATUS_OK, + "open policy handle on p3"); + + /* + * use policy handle on all 3 connections + */ + torture_comment(torture, "use the policy handle on p1,p2,p3\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b1, mem_ctx, &qsec), + "Query Security failed"); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "use policy handle on p1"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b2, mem_ctx, &qsec), + "Query Security failed"); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "use policy handle on p2"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b3, mem_ctx, &qsec), + "Query Security failed"); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_OK, + "use policy handle on p3"); + + /* + * close policy handle on connection 2 and the others get a fault + */ + torture_comment(torture, "close the policy handle on p2 others get a fault\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_Close_r(b2, mem_ctx, &c), + "Close failed"); + torture_assert_ntstatus_equal(torture, c.out.result, NT_STATUS_OK, + "closing policy handle on p2"); + + status = dcerpc_lsa_Close_r(b1, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p1 again"); + + status = dcerpc_lsa_Close_r(b3, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p3"); + + status = dcerpc_lsa_Close_r(b2, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p2 again"); + + /* + * open a new policy handle + */ + torture_comment(torture, "open a new policy handle on p1 and use it\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_OpenPolicy_r(b1, mem_ctx, &r), + "OpenPolicy failed"); + torture_assert_ntstatus_equal(torture, r.out.result, NT_STATUS_OK, + "open 2nd policy handle on p1"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b1, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "QuerySecurity handle on p1"); + + /* close first connection */ + torture_comment(torture, "disconnect p1\n"); + talloc_free(p1); + smb_msleep(5); + + /* + * and it's still available on p2,p3 + */ + torture_comment(torture, "use policy handle on p2,p3\n"); + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b2, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "QuerySecurity handle on p2 after p1 was disconnected"); + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b3, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "QuerySecurity handle on p3 after p1 was disconnected"); + + /* + * now open p4 + * and use the handle on it + */ + torture_comment(torture, "connect lsa pipe4 and use policy handle\n"); + status = torture_rpc_connection_transport(torture, &p4, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe4"); + b4 = p4->binding_handle; + + torture_assert_ntstatus_ok(torture, dcerpc_lsa_QuerySecurity_r(b4, mem_ctx, &qsec), + "QuerySecurity failed"); + torture_assert_ntstatus_equal(torture, qsec.out.result, NT_STATUS_OK, + "using policy handle on p4"); + + /* + * now close p2,p3,p4 + * without closing the policy handle + */ + torture_comment(torture, "disconnect p2,p3,p4\n"); + talloc_free(p2); + talloc_free(p3); + talloc_free(p4); + smb_msleep(10); + + /* + * now open p5 + */ + torture_comment(torture, "connect lsa pipe5 - should fail\n"); + status = torture_rpc_connection_transport(torture, &p5, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening lsa pipe5"); + + talloc_free(mem_ctx); + + return true; +} + + +static bool test_handles_samr(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2; + struct dcerpc_binding_handle *b1, *b2; + struct policy_handle handle; + struct policy_handle handle2; + struct samr_Connect r; + struct samr_Close c; + TALLOC_CTX *mem_ctx = talloc_new(torture); + + torture_comment(torture, "RPC-HANDLE-SAMR\n"); + + status = torture_rpc_connection(torture, &p1, &ndr_table_samr); + torture_assert_ntstatus_ok(torture, status, "opening samr pipe1"); + b1 = p1->binding_handle; + + status = torture_rpc_connection(torture, &p2, &ndr_table_samr); + torture_assert_ntstatus_ok(torture, status, "opening samr pipe2"); + b2 = p2->binding_handle; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.connect_handle = &handle; + + torture_assert_ntstatus_ok(torture, dcerpc_samr_Connect_r(b1, mem_ctx, &r), + "Connect failed"); + torture_assert_ntstatus_ok(torture, r.out.result, "opening policy handle on p1"); + + c.in.handle = &handle; + c.out.handle = &handle2; + + status = dcerpc_samr_Close_r(b2, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p2"); + + torture_assert_ntstatus_ok(torture, dcerpc_samr_Close_r(b1, mem_ctx, &c), + "Close failed"); + torture_assert_ntstatus_ok(torture, c.out.result, "closing policy handle on p1"); + + status = dcerpc_samr_Close_r(b1, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p1 again"); + + talloc_free(mem_ctx); + + return true; +} + +static bool test_handles_mixed_shared(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2, *p3, *p4, *p5, *p6; + struct dcerpc_binding_handle *b1, *b2; + struct policy_handle handle; + struct policy_handle handle2; + struct samr_Connect r; + struct lsa_Close lc; + struct samr_Close sc; + TALLOC_CTX *mem_ctx = talloc_new(torture); + enum dcerpc_transport_t transport; + uint32_t assoc_group_id; + + torture_comment(torture, "RPC-HANDLE-MIXED-SHARED\n"); + + torture_comment(torture, "connect samr pipe1\n"); + status = torture_rpc_connection(torture, &p1, &ndr_table_samr); + torture_assert_ntstatus_ok(torture, status, "opening samr pipe1"); + b1 = p1->binding_handle; + + transport = p1->conn->transport.transport; + assoc_group_id = dcerpc_binding_get_assoc_group_id(p1->binding); + + torture_comment(torture, "use assoc_group_id[0x%08X] for new connections\n", assoc_group_id); + + torture_comment(torture, "connect lsa pipe2\n"); + status = torture_rpc_connection_transport(torture, &p2, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe2"); + b2 = p2->binding_handle; + + torture_comment(torture, "got assoc_group_id[0x%08X] for p2\n", + dcerpc_binding_get_assoc_group_id(p2->binding)); + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.connect_handle = &handle; + + torture_comment(torture, "samr_Connect to open a policy handle on samr p1\n"); + torture_assert_ntstatus_ok(torture, dcerpc_samr_Connect_r(b1, mem_ctx, &r), + "Connect failed"); + torture_assert_ntstatus_ok(torture, r.out.result, "opening policy handle on p1"); + + lc.in.handle = &handle; + lc.out.handle = &handle2; + sc.in.handle = &handle; + sc.out.handle = &handle2; + + torture_comment(torture, "use policy handle on lsa p2 - should fail\n"); + status = dcerpc_lsa_Close_r(b2, mem_ctx, &lc); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing handle on lsa p2"); + + torture_comment(torture, "closing policy handle on samr p1\n"); + torture_assert_ntstatus_ok(torture, dcerpc_samr_Close_r(b1, mem_ctx, &sc), + "Close failed"); + torture_assert_ntstatus_ok(torture, sc.out.result, "closing policy handle on p1"); + + talloc_free(p1); + talloc_free(p2); + smb_msleep(10); + + torture_comment(torture, "connect samr pipe3 - should fail\n"); + status = torture_rpc_connection_transport(torture, &p3, &ndr_table_samr, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening samr pipe3"); + + torture_comment(torture, "connect lsa pipe4 - should fail\n"); + status = torture_rpc_connection_transport(torture, &p4, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening lsa pipe4"); + + /* + * We use ~assoc_group_id instead of p1->assoc_group_id, because + * this way we are less likely to use an id which is already in use. + */ + assoc_group_id = ~assoc_group_id; + torture_comment(torture, "connect samr pipe5 with assoc_group_id[0x%08X]- should fail\n", ++assoc_group_id); + status = torture_rpc_connection_transport(torture, &p5, &ndr_table_samr, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening samr pipe5"); + + torture_comment(torture, "connect lsa pipe6 with assoc_group_id[0x%08X]- should fail\n", ++assoc_group_id); + status = torture_rpc_connection_transport(torture, &p6, &ndr_table_lsarpc, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening lsa pipe6"); + + talloc_free(mem_ctx); + + return true; +} + +static bool test_handles_random_assoc(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2, *p3; + TALLOC_CTX *mem_ctx = talloc_new(torture); + enum dcerpc_transport_t transport; + uint32_t assoc_group_id; + + torture_comment(torture, "RPC-HANDLE-RANDOM-ASSOC\n"); + + torture_comment(torture, "connect samr pipe1\n"); + status = torture_rpc_connection(torture, &p1, &ndr_table_samr); + torture_assert_ntstatus_ok(torture, status, "opening samr pipe1"); + + torture_comment(torture, "pipe1 uses assoc_group_id[0x%08X]\n", + dcerpc_binding_get_assoc_group_id(p1->binding)); + + transport = p1->conn->transport.transport; + /* + * We use ~p1->assoc_group_id instead of p1->assoc_group_id, because + * this way we are less likely to use an id which is already in use. + * + * And make sure it doesn't wrap. + */ + assoc_group_id = dcerpc_binding_get_assoc_group_id(p1->binding); + assoc_group_id = ~MIN(assoc_group_id, UINT32_MAX - 3); + + torture_comment(torture, "connect samr pipe2 with assoc_group_id[0x%08X]- should fail\n", ++assoc_group_id); + status = torture_rpc_connection_transport(torture, &p2, &ndr_table_samr, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening samr pipe2"); + + torture_comment(torture, "connect samr pipe3 with assoc_group_id[0x%08X]- should fail\n", ++assoc_group_id); + status = torture_rpc_connection_transport(torture, &p3, &ndr_table_samr, + transport, + assoc_group_id, + 0); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_UNSUCCESSFUL, + "opening samr pipe3"); + + talloc_free(mem_ctx); + + return true; +} + + +static bool test_handles_drsuapi(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2; + struct dcerpc_binding_handle *b1, *b2; + struct policy_handle handle; + struct policy_handle handle2; + struct GUID bind_guid; + struct drsuapi_DsBind r; + struct drsuapi_DsUnbind c; + TALLOC_CTX *mem_ctx = talloc_new(torture); + + torture_comment(torture, "RPC-HANDLE-DRSUAPI\n"); + + status = torture_rpc_connection(torture, &p1, &ndr_table_drsuapi); + torture_assert_ntstatus_ok(torture, status, "opening drsuapi pipe1"); + b1 = p1->binding_handle; + + status = torture_rpc_connection(torture, &p2, &ndr_table_drsuapi); + torture_assert_ntstatus_ok(torture, status, "opening drsuapi pipe1"); + b2 = p2->binding_handle; + + GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid); + + r.in.bind_guid = &bind_guid; + r.in.bind_info = NULL; + r.out.bind_handle = &handle; + + status = dcerpc_drsuapi_DsBind_r(b1, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "drsuapi_DsBind not supported - skipping\n"); + talloc_free(mem_ctx); + return true; + } + + c.in.bind_handle = &handle; + c.out.bind_handle = &handle2; + + status = dcerpc_drsuapi_DsUnbind_r(b2, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p2"); + + status = dcerpc_drsuapi_DsUnbind_r(b1, mem_ctx, &c); + torture_assert_ntstatus_ok(torture, status, "closing policy handle on p1"); + + status = dcerpc_drsuapi_DsUnbind_r(b1, mem_ctx, &c); + torture_assert_ntstatus_equal(torture, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "closing policy handle on p1 again"); + + talloc_free(mem_ctx); + + return true; +} + +struct torture_suite *torture_rpc_handles(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + + suite = torture_suite_create(mem_ctx, "handles"); + torture_suite_add_simple_test(suite, "lsarpc", test_handles_lsa); + torture_suite_add_simple_test(suite, "lsarpc-shared", test_handles_lsa_shared); + torture_suite_add_simple_test(suite, "samr", test_handles_samr); + torture_suite_add_simple_test(suite, "mixed-shared", test_handles_mixed_shared); + torture_suite_add_simple_test(suite, "random-assoc", test_handles_random_assoc); + torture_suite_add_simple_test(suite, "drsuapi", test_handles_drsuapi); + return suite; +} diff --git a/source4/torture/rpc/initshutdown.c b/source4/torture/rpc/initshutdown.c new file mode 100644 index 0000000..28eaacd --- /dev/null +++ b/source4/torture/rpc/initshutdown.c @@ -0,0 +1,116 @@ +/* + Unix SMB/CIFS implementation. + test suite for initshutdown operations + + Copyright (C) Tim Potter 2003 + Copyright (C) Jelmer Vernooij 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 "librpc/gen_ndr/ndr_initshutdown_c.h" +#include "torture/rpc/torture_rpc.h" + +static void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s) +{ + name->string = s; +} + + +static bool test_Abort(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct initshutdown_Abort r; + NTSTATUS status; + uint16_t server = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server = &server; + + status = dcerpc_initshutdown_Abort_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, + "initshutdown_Abort failed"); + + torture_assert_werr_ok(tctx, r.out.result, "initshutdown_Abort failed"); + + return true; +} + +static bool test_Init(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct initshutdown_Init r; + NTSTATUS status; + uint16_t hostname = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.hostname = &hostname; + r.in.message = talloc(tctx, struct lsa_StringLarge); + init_lsa_StringLarge(r.in.message, "spottyfood"); + r.in.force_apps = 1; + r.in.timeout = 30; + r.in.do_reboot = 1; + + status = dcerpc_initshutdown_Init_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "initshutdown_Init failed"); + torture_assert_werr_ok(tctx, r.out.result, "initshutdown_Init failed"); + + return test_Abort(tctx, p); +} + +static bool test_InitEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct initshutdown_InitEx r; + NTSTATUS status; + uint16_t hostname = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.hostname = &hostname; + r.in.message = talloc(tctx, struct lsa_StringLarge); + init_lsa_StringLarge(r.in.message, "spottyfood"); + r.in.force_apps = 1; + r.in.timeout = 30; + r.in.do_reboot = 1; + r.in.reason = 0; + + status = dcerpc_initshutdown_InitEx_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "initshutdown_InitEx failed"); + + torture_assert_werr_ok(tctx, r.out.result, "initshutdown_InitEx failed"); + + return test_Abort(tctx, p); +} + + +struct torture_suite *torture_rpc_initshutdown(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "initshutdown"); + struct torture_rpc_tcase *tcase; + struct torture_test *test; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "initshutdown", + &ndr_table_initshutdown); + + test = torture_rpc_tcase_add_test(tcase, "Init", test_Init); + test->dangerous = true; + test = torture_rpc_tcase_add_test(tcase, "InitEx", test_InitEx); + test->dangerous = true; + + return suite; +} diff --git a/source4/torture/rpc/iremotewinspool.c b/source4/torture/rpc/iremotewinspool.c new file mode 100644 index 0000000..3e8010d --- /dev/null +++ b/source4/torture/rpc/iremotewinspool.c @@ -0,0 +1,855 @@ +/* + Unix SMB/CIFS implementation. + test suite for iremotewinspool rpc operations + + Copyright (C) Guenther Deschner 2013 + + 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 "torture/torture.h" +#include "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_winspool_c.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/registry/util_reg.h" +#include "torture/rpc/iremotewinspool_common.h" + +static bool torture_rpc_iremotewinspool_setup_common(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + const char *printer_name; + struct spoolss_UserLevel1 client_info; + struct dcerpc_binding *binding; + + torture_assert_ntstatus_ok(tctx, + GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &t->object_uuid), + "failed to parse GUID"); + + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding), + "failed to retrieve torture binding"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_binding_set_object(binding, t->object_uuid), + "failed to set object_uuid"); + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection_with_binding(tctx, binding, &t->iremotewinspool_pipe, &ndr_table_iremotewinspool), + "Error connecting to server"); + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(t->iremotewinspool_pipe)); + + client_info = test_get_client_info(tctx, WIN_7, 6, 1, "testclient_machine", "testclient_user"); + + torture_assert(tctx, + test_AsyncOpenPrinter_byprinter(tctx, t, + t->iremotewinspool_pipe, printer_name, + client_info, &t->server_handle), + "failed to open printserver"); + torture_assert(tctx, + test_get_environment(tctx, + t->iremotewinspool_pipe->binding_handle, + &t->server_handle, &t->environment), + "failed to get environment"); + + return true; +} + +static bool torture_rpc_iremotewinspool_setup(struct torture_context *tctx, + void **data) +{ + struct test_iremotewinspool_context *t; + + *data = t = talloc_zero(tctx, struct test_iremotewinspool_context); + + return torture_rpc_iremotewinspool_setup_common(tctx, t); +} + +static bool torture_rpc_iremotewinspool_teardown_common(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + + test_AsyncClosePrinter_byhandle(tctx, t, t->iremotewinspool_pipe, &t->server_handle); + + return true; +} + +static bool torture_rpc_iremotewinspool_teardown(struct torture_context *tctx, + void *data) +{ + struct test_iremotewinspool_context *t = talloc_get_type(data, struct test_iremotewinspool_context); + bool ret; + + ret = torture_rpc_iremotewinspool_teardown_common(tctx, t); + talloc_free(t); + + return ret; +} + +static bool test_AsyncClosePrinter(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + const char *printer_name; + struct spoolss_UserLevel1 client_info; + struct policy_handle handle; + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + client_info = test_get_client_info(tctx, WIN_7, 6, 1, "testclient_machine", "testclient_user"); + + torture_assert(tctx, + test_AsyncOpenPrinter_byprinter(tctx, ctx, p, printer_name, client_info, &handle), + "failed to test AsyncOpenPrinter"); + + torture_assert(tctx, + test_AsyncClosePrinter_byhandle(tctx, ctx, p, &handle), + "failed to test AsyncClosePrinter"); + + return true; +} + +static bool test_AsyncOpenPrinter(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + const char *printer_name; + struct spoolss_UserLevel1 client_info; + struct policy_handle handle; + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + client_info = test_get_client_info(tctx, WIN_7, 6, 1, "testclient_machine", "testclient_user"); + + torture_assert(tctx, + test_AsyncOpenPrinter_byprinter(tctx, ctx, p, printer_name, client_info, &handle), + "failed to test AsyncOpenPrinter"); + + test_AsyncClosePrinter_byhandle(tctx, ctx, p, &handle); + + return true; +} + +/* + * Validate the result of AsyncOpenPrinter calls based on client info + * build number. Windows Server 2016 rejects an advertised build + * number less than 6000(Windows Vista and Windows Server 2008, or older) + */ +static bool test_AsyncOpenPrinterValidateBuildNumber(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + const char *printer_name; + struct spoolss_UserLevel1 client_info; + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_DevmodeContainer devmode_ctr; + struct spoolss_UserLevelCtr client_info_ctr = { + .level = 1, + }; + uint32_t access_mask = SERVER_ALL_ACCESS; + struct winspool_AsyncOpenPrinter r; + NTSTATUS status; + bool ok = false; + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_assert_not_null(tctx, printer_name, "Cannot allocate memory"); + + /* fail with Windows 2000 build number */ + client_info = test_get_client_info(tctx, WIN_2000, 3, SPOOLSS_MINOR_VERSION_0, + "testclient_machine", "testclient_user"); + + ZERO_STRUCT(devmode_ctr); + + client_info_ctr.user_info.level1 = &client_info; + + r.in.pPrinterName = printer_name; + r.in.pDatatype = NULL; + r.in.pDevModeContainer = &devmode_ctr; + r.in.AccessRequired = access_mask; + r.in.pClientInfo = &client_info_ctr; + r.out.pHandle = &handle; + + status = dcerpc_winspool_AsyncOpenPrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "AsyncOpenPrinter failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_ACCESS_DENIED, + "AsyncOpenPrinter should have failed"); + + /* succeed with Windows 7 build number */ + client_info = test_get_client_info(tctx, WIN_7, 6, 1, + "testclient_machine", "testclient_user"); + client_info_ctr.user_info.level1 = &client_info; + r.in.pClientInfo = &client_info_ctr; + + status = dcerpc_winspool_AsyncOpenPrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "AsyncOpenPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, + "AsyncOpenPrinter failed"); + + ok = test_AsyncClosePrinter_byhandle(tctx, ctx, p, &handle); + torture_assert(tctx, ok, "failed to AsyncClosePrinter handle"); + + return true; + +} + +static struct spoolss_NotifyOption *setup_printserver_NotifyOption(struct torture_context *tctx) +{ + struct spoolss_NotifyOption *o; + + o = talloc_zero(tctx, struct spoolss_NotifyOption); + if (o == NULL) { + return NULL; + } + + o->version = 2; + o->flags = PRINTER_NOTIFY_OPTIONS_REFRESH; + + o->count = 2; + o->types = talloc_zero_array(o, struct spoolss_NotifyOptionType, o->count); + if (o->types == NULL) { + talloc_free(o); + return NULL; + } + + o->types[0].type = PRINTER_NOTIFY_TYPE; + o->types[0].count = 1; + o->types[0].fields = talloc_array(o->types, union spoolss_Field, o->types[0].count); + if (o->types[0].fields == NULL) { + talloc_free(o); + return NULL; + } + o->types[0].fields[0].field = PRINTER_NOTIFY_FIELD_SERVER_NAME; + + o->types[1].type = JOB_NOTIFY_TYPE; + o->types[1].count = 1; + o->types[1].fields = talloc_array(o->types, union spoolss_Field, o->types[1].count); + if (o->types[1].fields == NULL) { + talloc_free(o); + return NULL; + } + o->types[1].fields[0].field = JOB_NOTIFY_FIELD_MACHINE_NAME; + + return o; +} + +static bool test_SyncUnRegisterForRemoteNotifications_args(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *notify_handle) +{ + struct winspool_SyncUnRegisterForRemoteNotifications r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.phRpcHandle = notify_handle; + r.out.phRpcHandle = notify_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_SyncUnRegisterForRemoteNotifications_r(b, tctx, &r), + "SyncUnRegisterForRemoteNotifications failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "SyncUnRegisterForRemoteNotifications failed"); + + return true; +} + +static bool test_SyncRegisterForRemoteNotifications_args(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *server_handle, + struct policy_handle *notify_handle); + +static bool test_SyncUnRegisterForRemoteNotifications(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + struct policy_handle notify_handle; + + torture_assert(tctx, + test_SyncRegisterForRemoteNotifications_args(tctx, + ctx->iremotewinspool_pipe, + &ctx->server_handle, + ¬ify_handle), + "failed to test SyncRegisterForRemoteNotifications"); + + torture_assert(tctx, + test_SyncUnRegisterForRemoteNotifications_args(tctx, + ctx->iremotewinspool_pipe, + ¬ify_handle), + "failed to test UnSyncRegisterForRemoteNotifications"); + + return true; +} + +static bool test_SyncRegisterForRemoteNotifications_args(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *server_handle, + struct policy_handle *notify_handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + + struct winspool_SyncRegisterForRemoteNotifications r; + struct winspool_PrintPropertiesCollection NotifyFilter; + struct winspool_PrintNamedProperty *c; + struct spoolss_NotifyOption *options; + + ZERO_STRUCT(NotifyFilter); + + options = setup_printserver_NotifyOption(tctx); + torture_assert(tctx, options, "out of memory"); + + c = talloc_zero_array(tctx, struct winspool_PrintNamedProperty, 4); + torture_assert(tctx, c, "out of memory"); + + c[0].propertyName = "RemoteNotifyFilter Flags"; + c[0].propertyValue.PropertyType = winspool_PropertyTypeInt32; + c[0].propertyValue.value.propertyInt32 = 0xff; + + c[1].propertyName = "RemoteNotifyFilter Options"; + c[1].propertyValue.PropertyType = winspool_PropertyTypeInt32; + c[1].propertyValue.value.propertyInt32 = 0; + + c[2].propertyName = "RemoteNotifyFilter Color"; + c[2].propertyValue.PropertyType = winspool_PropertyTypeInt32; + c[2].propertyValue.value.propertyInt32 = 0; + + c[3].propertyName = "RemoteNotifyFilter NotifyOptions"; + c[3].propertyValue.PropertyType = winspool_PropertyTypeNotificationOptions; + c[3].propertyValue.value.propertyOptionsContainer.pOptions = options; + + NotifyFilter.numberOfProperties = 4; + NotifyFilter.propertiesCollection = c; + + r.in.hPrinter = *server_handle; + r.in.pNotifyFilter = &NotifyFilter; + r.out.phRpcHandle = notify_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_SyncRegisterForRemoteNotifications_r(b, tctx, &r), + "SyncRegisterForRemoteNotifications failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "SyncRegisterForRemoteNotifications failed"); + + return true; +} + +static bool test_SyncRegisterForRemoteNotifications(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + struct policy_handle notify_handle; + + torture_assert(tctx, + test_SyncRegisterForRemoteNotifications_args(tctx, + ctx->iremotewinspool_pipe, + &ctx->server_handle, + ¬ify_handle), + "failed to test SyncRegisterForRemoteNotifications"); + + test_SyncUnRegisterForRemoteNotifications_args(tctx, ctx->iremotewinspool_pipe, ¬ify_handle); + + return true; +} + +static bool test_AsyncUploadPrinterDriverPackage(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct winspool_AsyncUploadPrinterDriverPackage r; + uint32_t pcchDestInfPath = 0; + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.pszInfPath = ""; + r.in.pszEnvironment = ""; + r.in.dwFlags = 0; + r.in.pszDestInfPath = NULL; + r.in.pcchDestInfPath = &pcchDestInfPath; + r.out.pszDestInfPath = NULL; + r.out.pcchDestInfPath = &pcchDestInfPath; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncUploadPrinterDriverPackage_r(b, tctx, &r), + "AsyncUploadPrinterDriverPackage failed"); + torture_assert_hresult_equal(tctx, r.out.result, HRES_E_INVALIDARG, + "AsyncUploadPrinterDriverPackage failed"); + + pcchDestInfPath = 260; + r.in.pszDestInfPath = talloc_zero(tctx, const char); + r.out.pszDestInfPath = talloc_zero(tctx, const char); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncUploadPrinterDriverPackage_r(b, tctx, &r), + "AsyncUploadPrinterDriverPackage failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INVALID_ENVIRONMENT, + "AsyncUploadPrinterDriverPackage failed"); + + r.in.pszEnvironment = SPOOLSS_ARCHITECTURE_x64; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncUploadPrinterDriverPackage_r(b, tctx, &r), + "AsyncUploadPrinterDriverPackage failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_FILE_NOT_FOUND, + "AsyncUploadPrinterDriverPackage failed"); + + r.in.pszInfPath = "\\\\mthelena\\print$\\x64\\{BD443844-ED00-4D96-8CAE-95E49492312A}\\prnbrcl1.inf"; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncUploadPrinterDriverPackage_r(b, tctx, &r), + "AsyncUploadPrinterDriverPackage failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_FILE_NOT_FOUND, + "AsyncUploadPrinterDriverPackage failed"); + + return true; +} + +static bool test_AsyncEnumPrinters(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct winspool_AsyncEnumPrinters r; + uint32_t levels[] = { 1, 2, /*3,*/ 4, 5 }; + int i; + + uint32_t needed; + uint32_t returned; + + for (i = 0; i < ARRAY_SIZE(levels); i++) { + + r.in.Flags = PRINTER_ENUM_LOCAL; + r.in.pName = NULL; + r.in.Level = levels[i]; + r.in.cbBuf = 0; + r.in.pPrinterEnum = NULL; + r.out.pcbNeeded = &needed; + r.out.pcReturned = &returned; + r.out.pPrinterEnum = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncEnumPrinters_r(b, tctx, &r), + "AsyncEnumPrinters failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "AsyncEnumPrinters failed"); + + r.in.cbBuf = needed; + r.in.pPrinterEnum = talloc_zero_array(tctx, uint8_t, r.in.cbBuf); + r.out.pPrinterEnum = r.in.pPrinterEnum; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncEnumPrinters_r(b, tctx, &r), + "AsyncEnumPrinters failed"); + torture_assert_werr_ok(tctx, r.out.result, + "AsyncEnumPrinters failed"); + } + + return true; +} + +static bool test_AsyncGetPrinterData(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + DATA_BLOB blob; + const char *s; + bool ok; + + uint32_t pType; + uint32_t pcbNeeded; + uint8_t *pData; + + torture_assert(tctx, + test_AsyncGetPrinterData_args(tctx, b, &ctx->server_handle, + "MajorVersion", + &pType, &pData, &pcbNeeded), + "failed to check for MajorVersion"); + + torture_assert_int_equal(tctx, pcbNeeded, 4, "pcbNeeded"); + torture_assert_int_equal(tctx, pType, REG_DWORD, "pType"); + torture_assert_int_equal(tctx, IVAL(pData, 0), 3, "pData"); + + torture_assert(tctx, + test_AsyncGetPrinterData_args(tctx, b, &ctx->server_handle, + "Architecture", + &pType, &pData, &pcbNeeded), + "failed to check for Architecture"); + + blob = data_blob_const(pData, pcbNeeded); + + torture_assert_int_equal(tctx, pType, REG_SZ, "pType"); + torture_assert(tctx, pull_reg_sz(tctx, &blob, &s), ""); + ok = strequal(s, SPOOLSS_ARCHITECTURE_x64) || strequal(s, SPOOLSS_ARCHITECTURE_NT_X86); + torture_assert(tctx, ok, "unexpected architecture returned"); + + return true; +} + +static bool test_AsyncCorePrinterDriverInstalled(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct winspool_AsyncCorePrinterDriverInstalled r; + int32_t pbDriverInstalled; + struct GUID guid; + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.pszEnvironment = ""; + r.in.CoreDriverGUID = GUID_zero(); + r.in.ftDriverDate = 0; + r.in.dwlDriverVersion = 0; + r.out.pbDriverInstalled = &pbDriverInstalled; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INVALID_ENVIRONMENT, + "AsyncCorePrinterDriverInstalled failed"); + + r.in.pszEnvironment = SPOOLSS_ARCHITECTURE_x64; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, false, + "unexpected driver installed"); + + r.in.CoreDriverGUID = GUID_random(); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, false, + "unexpected driver installed"); + + torture_assert_ntstatus_ok(tctx, + GUID_from_string(SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV, &guid), ""); + + r.in.CoreDriverGUID = guid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, true, + "xps core driver not installed?"); + + r.in.dwlDriverVersion = 0xffffffff; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, true, + "xps core driver not installed?"); + + r.in.dwlDriverVersion = 1234; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, true, + "xps core driver not installed?"); + + r.in.ftDriverDate = unix_timespec_to_nt_time(timespec_current()); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, false, + "driver too old ?"); + + r.in.dwlDriverVersion = 0; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncCorePrinterDriverInstalled_r(b, tctx, &r), + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "AsyncCorePrinterDriverInstalled failed"); + torture_assert_int_equal(tctx, *r.out.pbDriverInstalled, false, + "unexpected driver installed"); + + return true; +} + +static bool test_get_core_printer_drivers_arch_guid(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *architecture, + const char *guid_str, + const char **package_id) +{ + struct winspool_AsyncGetCorePrinterDrivers r; + DATA_BLOB blob; + const char **s; + struct dcerpc_binding_handle *b = p->binding_handle; + + s = talloc_zero_array(tctx, const char *, 2); + s[0] = guid_str; + + torture_assert(tctx, + push_reg_multi_sz(tctx, &blob, s), + "push_reg_multi_sz failed"); + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.pszEnvironment = architecture; + r.in.cchCoreDrivers = blob.length/2; + r.in.pszzCoreDriverDependencies = (uint16_t *)blob.data; + r.in.cCorePrinterDrivers = 1; + r.out.pCorePrinterDrivers = talloc_zero_array(tctx, struct spoolss_CorePrinterDriver, r.in.cCorePrinterDrivers); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncGetCorePrinterDrivers_r(b, tctx, &r), + "winspool_AsyncCorePrinterDrivers failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "winspool_AsyncCorePrinterDrivers failed"); + + if (package_id) { + *package_id = r.out.pCorePrinterDrivers[0].szPackageID; + } + + return true; +} + +static bool test_AsyncDeletePrintDriverPackage(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct winspool_AsyncDeletePrinterDriverPackage r; + + const char *architectures[] = { +/* SPOOLSS_ARCHITECTURE_NT_X86, */ + SPOOLSS_ARCHITECTURE_x64 + }; + int i; + + for (i=0; i < ARRAY_SIZE(architectures); i++) { + + const char *package_id; + + torture_assert(tctx, + test_get_core_printer_drivers_arch_guid(tctx, p, + architectures[i], + SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV, + &package_id), + "failed to get core printer driver"); + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.pszEnvironment = ""; + r.in.pszInfPath = ""; + + torture_comment(tctx, "Testing AsyncDeletePrinterDriverPackage(%s, %s, %s)\n", + r.in.pszServer, architectures[i], package_id); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncDeletePrinterDriverPackage_r(b, tctx, &r), + "AsyncDeletePrinterDriverPackage failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_NOT_FOUND, + "AsyncDeletePrinterDriverPackage failed"); + + r.in.pszInfPath = package_id; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncDeletePrinterDriverPackage_r(b, tctx, &r), + "AsyncDeletePrinterDriverPackage failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INVALID_ENVIRONMENT, + "AsyncDeletePrinterDriverPackage failed"); + + r.in.pszEnvironment = architectures[i]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncDeletePrinterDriverPackage_r(b, tctx, &r), + "AsyncDeletePrinterDriverPackage failed"); + torture_assert_hresult_equal(tctx, r.out.result, HRES_E_ACCESSDENIED, + "AsyncDeletePrinterDriverPackage failed"); + } + + return true; +} + +static bool test_AsyncGetPrinterDriverDirectory(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct winspool_AsyncGetPrinterDriverDirectory r; + uint32_t pcbNeeded; + DATA_BLOB blob; + const char *s; + + r.in.pName = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.pEnvironment = ctx->environment; + r.in.Level = 1; + r.in.cbBuf = 0x200; + r.in.pDriverDirectory = talloc_zero_array(tctx, uint8_t, r.in.cbBuf); + r.out.pcbNeeded = &pcbNeeded; + r.out.pDriverDirectory = r.in.pDriverDirectory; + + torture_comment(tctx, "Testing AsyncGetPrinterDriverDirectory(%s, %s)\n", + r.in.pName, r.in.pEnvironment); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winspool_AsyncGetPrinterDriverDirectory_r(b, tctx, &r), + "AsyncGetPrinterDriverDirectory failed"); + torture_assert_werr_ok(tctx, r.out.result, + "AsyncGetPrinterDriverDirectory failed"); + + blob = data_blob_const(r.out.pDriverDirectory, pcbNeeded); + + torture_assert(tctx, + pull_reg_sz(tctx, &blob, &s), + "failed to pull reg_sz"); + + torture_comment(tctx, "got: %s\n", s); + + return true; +} + +/* + * Test if one can close a printserver handle that has been acquired via + * winspool_AsyncOpenPrinter with a spoolss_ClosePrinter operation. + */ + +static bool test_OpenPrinter(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + const char *printer_name; + struct policy_handle handle; + struct dcerpc_pipe *s; + struct dcerpc_binding *binding; + struct spoolss_UserLevel1 client_info; + struct spoolss_ClosePrinter r; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding), + "failed to get binding"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_binding_set_transport(binding, NCACN_NP), + "failed to set ncacn_np transport"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_binding_set_object(binding, GUID_zero()), + "failed to set object uuid to zero"); + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection_with_binding(tctx, binding, &s, &ndr_table_spoolss), + "failed to connect to spoolss"); + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + client_info = test_get_client_info(tctx, WIN_7, 6, 1, "testclient_machine", "testclient_user"); + + torture_assert(tctx, + test_AsyncOpenPrinter_byprinter(tctx, ctx, p, printer_name, client_info, &handle), + "failed to open printserver via winspool"); + + + r.in.handle = &handle; + r.out.handle = &handle; + + torture_assert_ntstatus_equal(tctx, + dcerpc_spoolss_ClosePrinter_r(s->binding_handle, tctx, &r), + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "ClosePrinter failed"); + + talloc_free(s); + + return true; +} + +struct torture_suite *torture_rpc_iremotewinspool(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "iremotewinspool"); + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "printserver"); + + torture_tcase_set_fixture(tcase, + torture_rpc_iremotewinspool_setup, + torture_rpc_iremotewinspool_teardown); + + torture_tcase_add_simple_test(tcase, "AsyncOpenPrinter", test_AsyncOpenPrinter); + torture_tcase_add_simple_test(tcase, "SyncRegisterForRemoteNotifications", test_SyncRegisterForRemoteNotifications); + torture_tcase_add_simple_test(tcase, "SyncUnRegisterForRemoteNotifications", test_SyncUnRegisterForRemoteNotifications); + torture_tcase_add_simple_test(tcase, "AsyncClosePrinter", test_AsyncClosePrinter); + torture_tcase_add_simple_test(tcase, "AsyncUploadPrinterDriverPackage", test_AsyncUploadPrinterDriverPackage); + torture_tcase_add_simple_test(tcase, "AsyncEnumPrinters", test_AsyncEnumPrinters); + torture_tcase_add_simple_test(tcase, "AsyncGetPrinterData", test_AsyncGetPrinterData); + torture_tcase_add_simple_test(tcase, "AsyncCorePrinterDriverInstalled", test_AsyncCorePrinterDriverInstalled); + torture_tcase_add_simple_test(tcase, "AsyncDeletePrintDriverPackage", test_AsyncDeletePrintDriverPackage); + torture_tcase_add_simple_test(tcase, "AsyncGetPrinterDriverDirectory", test_AsyncGetPrinterDriverDirectory); + torture_tcase_add_simple_test(tcase, "AsyncOpenPrinterValidateBuildNumber", test_AsyncOpenPrinterValidateBuildNumber); + + tcase = torture_suite_add_tcase(suite, "handles"); + + torture_tcase_set_fixture(tcase, + torture_rpc_iremotewinspool_setup, + torture_rpc_iremotewinspool_teardown); + + torture_tcase_add_simple_test(tcase, "OpenPrinter", test_OpenPrinter); + + return suite; +} diff --git a/source4/torture/rpc/iremotewinspool_common.c b/source4/torture/rpc/iremotewinspool_common.c new file mode 100644 index 0000000..33dd469 --- /dev/null +++ b/source4/torture/rpc/iremotewinspool_common.c @@ -0,0 +1,247 @@ +#include "includes.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_winspool_c.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/registry/util_reg.h" +#include "torture/rpc/iremotewinspool_common.h" +#include "lib/printer_driver/printer_driver.h" + +void init_winreg_String(struct winreg_String *name, const char *s) +{ + name->name = s; + if (s != NULL) { + name->name_len = 2 * (strlen_m(s) + 1); + name->name_size = name->name_len; + } else { + name->name_len = 0; + name->name_size = 0; + } +} + +struct spoolss_UserLevel1 test_get_client_info(struct torture_context *tctx, + enum client_os_version os, + enum spoolss_MajorVersion major_number, + enum spoolss_MinorVersion minor_number, + const char *machine, + const char *user) +{ + struct spoolss_UserLevel1 level1; + + level1.size = 28; + level1.client = talloc_asprintf(tctx, "\\\\%s", machine); + level1.user = user; + level1.processor = PROCESSOR_ARCHITECTURE_AMD64; + level1.major = major_number; + level1.minor = minor_number; + + if (os == WIN_SERVER_2016 || os == WIN_10) { + level1.build = 10586; + } else if (os == WIN_SERVER_2012 || os == WIN_8) { + level1.build = 9200; + } else if (os == WIN_SERVER_2008R2 || os == WIN_7) { + level1.build = 7007; + } else if (os == WIN_SERVER_2008 || os == WIN_VISTA) { + level1.build = 6000; + } else if (os == WIN_2000) { + level1.build = 1382; + } + + return level1; +} + +bool test_AsyncOpenPrinter_byprinter(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx, + struct dcerpc_pipe *p, + const char *printer_name, + struct spoolss_UserLevel1 cinfo, + struct policy_handle *handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_DevmodeContainer devmode_ctr; + struct spoolss_UserLevelCtr client_info_ctr; + uint32_t access_mask = SERVER_ALL_ACCESS; + struct winspool_AsyncOpenPrinter r; + NTSTATUS status; + bool ok = true; + + ZERO_STRUCT(devmode_ctr); + + client_info_ctr.level = 1; + client_info_ctr.user_info.level1 = &cinfo; + + r.in.pPrinterName = printer_name; + r.in.pDatatype = NULL; + r.in.pDevModeContainer = &devmode_ctr; + r.in.AccessRequired = access_mask; + r.in.pClientInfo = &client_info_ctr; + r.out.pHandle = handle; + + status = dcerpc_winspool_AsyncOpenPrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncOpenPrinter failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "AsyncOpenPrinter failed"); + +done: + + return ok; +} + +bool test_get_environment(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char **architecture) +{ + DATA_BLOB blob; + enum winreg_Type type; + uint8_t *data; + uint32_t needed; + bool ok; + + ok = test_AsyncGetPrinterData_args(tctx, b, handle, "Architecture", &type, &data, &needed); + torture_assert(tctx, ok, "failed to get Architecture"); + + torture_assert_int_equal(tctx, type, REG_SZ, "unexpected type"); + + blob = data_blob_const(data, needed); + + torture_assert(tctx, + pull_reg_sz(tctx, &blob, architecture), + "failed to pull environment"); + + return true; +} + +bool test_AsyncClosePrinter_byhandle(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + + struct winspool_AsyncClosePrinter r; + NTSTATUS status; + bool ok = true; + + r.in.phPrinter = handle; + r.out.phPrinter = handle; + + status = dcerpc_winspool_AsyncClosePrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncClosePrinter failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "AsyncClosePrinter failed"); + +done: + + return ok; +} + +static bool test_AsyncGetPrinterData_checktype(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *expected_type, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + struct winspool_AsyncGetPrinterData r; + enum winreg_Type type; + uint32_t needed; + NTSTATUS status; + bool ok = true; + + r.in.hPrinter = *handle; + r.in.pValueName = value_name; + r.in.nSize = 0; + r.out.pType = &type; + r.out.pData = talloc_zero_array(tctx, uint8_t, r.in.nSize); + r.out.pcbNeeded = &needed; + + torture_comment(tctx, "Testing AsyncGetPrinterData(%s)\n", + r.in.pValueName); + + status = dcerpc_winspool_AsyncGetPrinterData_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncGetPrinterData failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + if (expected_type) { + torture_assert_int_equal(tctx, type, *expected_type, "unexpected type"); + } + r.in.nSize = needed; + r.out.pData = talloc_zero_array(tctx, uint8_t, r.in.nSize); + + status = dcerpc_winspool_AsyncGetPrinterData_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncGetPrinterData failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "AsyncGetPrinterData failed"); + + if (type_p) { + *type_p = type; + } + + if (data_p) { + *data_p = r.out.pData; + } + + if (needed_p) { + *needed_p = needed; + } + +done: + + return ok; +} + +bool test_AsyncGetPrinterData_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + return test_AsyncGetPrinterData_checktype(tctx, b, handle, + value_name, + NULL, + type_p, data_p, needed_p); +} + +/* Parse a driver inf file */ +bool parse_inf_driver(struct torture_context *tctx, + const char *driver_name, + const char *abs_inf_path, + const char *driver_arch, + const char *core_driver_inf, + struct spoolss_AddDriverInfo8 **_parsed_dinfo) +{ + struct spoolss_AddDriverInfo8 *drv_info; + const char *source_disk_name = NULL; + NTSTATUS status; + bool ok = true; + + drv_info = talloc_zero(tctx, struct spoolss_AddDriverInfo8); + torture_assert_not_null_goto(tctx, drv_info, ok, done, "Cannot allocate memory"); + + status = driver_inf_parse(tctx, + core_driver_inf, + abs_inf_path, + driver_arch, + driver_name, + drv_info, + &source_disk_name); + + if (NT_STATUS_EQUAL(status, NT_STATUS_DRIVER_INTERNAL_ERROR)) { + torture_comment(tctx, "--- Verify the correct torture option:driver_name is provided\n"); + } + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to parse driver inf\n"); + + *_parsed_dinfo = drv_info; +done: + return ok; +} diff --git a/source4/torture/rpc/iremotewinspool_common.h b/source4/torture/rpc/iremotewinspool_common.h new file mode 100644 index 0000000..fb6efd9 --- /dev/null +++ b/source4/torture/rpc/iremotewinspool_common.h @@ -0,0 +1,101 @@ +/* + Unix SMB/CIFS implementation. + + iremotewinspool rpc test operations + + Copyright (C) 2018 Justin Stephenson + + 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 "torture/rpc/torture_rpc.h" + +#define REG_DRIVER_CONTROL_KEY "SYSTEM\\CurrentControlSet\\Control\\Print" + +struct test_driver_info { + struct smbcli_state *cli; + struct spoolss_AddDriverInfo8 *info; + const char *local_driver_path; + size_t driver_path_len; + char *server_name; + char *share_name; + char *print_upload_guid_dir; + const char *inf_file; + const char *uploaded_inf_path; + const char *driver_name; + const char *driver_arch; + const char *core_driver_inf; +}; + +struct test_iremotewinspool_context { + struct GUID object_uuid; + struct dcerpc_pipe *iremotewinspool_pipe; + struct policy_handle server_handle; + struct test_driver_info *dinfo; + const char *environment; +}; + +enum client_os_version +{ + WIN_2000, + WIN_VISTA, + WIN_SERVER_2008, + WIN_7, + WIN_SERVER_2008R2, + WIN_8, + WIN_SERVER_2012, + WIN_10, + WIN_SERVER_2016 +}; + +void init_winreg_String(struct winreg_String *name, const char *s); + +struct spoolss_UserLevel1 test_get_client_info(struct torture_context *tctx, + enum client_os_version os, + enum spoolss_MajorVersion major_number, + enum spoolss_MinorVersion minor_number, + const char *machine, + const char *user); + +bool test_AsyncOpenPrinter_byprinter(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx, + struct dcerpc_pipe *p, + const char *printer_name, + struct spoolss_UserLevel1 cinfo, + struct policy_handle *handle); + +bool test_get_environment(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char **architecture); + +bool test_AsyncClosePrinter_byhandle(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx, + struct dcerpc_pipe *p, + struct policy_handle *handle); + +bool test_AsyncGetPrinterData_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p); + +bool parse_inf_driver(struct torture_context *tctx, + const char *driver_name, + const char *abs_inf_path, + const char *driver_arch, + const char *core_driver_inf, + struct spoolss_AddDriverInfo8 **_parsed_dinfo); diff --git a/source4/torture/rpc/iremotewinspool_driver.c b/source4/torture/rpc/iremotewinspool_driver.c new file mode 100644 index 0000000..f88b13a --- /dev/null +++ b/source4/torture/rpc/iremotewinspool_driver.c @@ -0,0 +1,840 @@ +/* + Unix SMB/CIFS implementation. + test suite for iremotewinspool driver rpc operations + + Copyright (C) Justin Stephenson 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/>. +*/ + +#include <dirent.h> +#include <talloc.h> +#include <libgen.h> +#include "includes.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/ndr_winspool.h" +#include "librpc/gen_ndr/ndr_winspool_c.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_winreg_c.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/registry/util_reg.h" +#include "torture/rpc/iremotewinspool_common.h" +#include "libcli/libcli.h" +#include "param/param.h" +#include "lib/registry/registry.h" +#include "libcli/libcli.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/resolve/resolve.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "lib/cmdline/cmdline.h" +#include "system/filesys.h" +#include "lib/util/tftw.h" + +/* Connect to print driver share //server_name/share */ +static bool smb_connect_print_share(struct torture_context *tctx, + const char *server_name, + const char *share_name, + struct smbcli_state **cli) +{ + NTSTATUS status; + bool ok = true; + + struct smbcli_options smb_options; + struct smbcli_session_options smb_session_options; + + torture_comment(tctx, "Connecting to printer driver share '//%s/%s'\n", + server_name, share_name); + + lpcfg_smbcli_options(tctx->lp_ctx, &smb_options); + lpcfg_smbcli_session_options(tctx->lp_ctx, &smb_session_options); + + /* On Windows, SMB1 must be enabled! */ + status = smbcli_full_connection(tctx, cli, server_name, + lpcfg_smb_ports(tctx->lp_ctx), + share_name, NULL, + lpcfg_socket_options(tctx->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(tctx->lp_ctx), + tctx->ev, + &smb_options, + &smb_session_options, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to connect to print$ share"); + +done: + + return ok; +} + +/* Copy file to destination where dst_fpath is a smb share path, + * files are either created or overwritten */ +static bool smb_copy_files(TALLOC_CTX *tctx, + const char *fpath, + const char *dst_fpath, + struct test_driver_info *dinfo) +{ + FILE *fp; + int smbfp = 0; + char *buffer = NULL; + int maxwrite = 64512; + size_t nread; + ssize_t nwrote; + bool ok = true; + size_t total_read; + + fp = fopen(fpath, "r"); + torture_assert_goto(tctx, fp, ok, done, "Failed to open local file\n"); + + smbfp = smbcli_open(dinfo->cli->tree, dst_fpath, O_RDWR|O_CREAT|O_TRUNC, DENY_NONE); + torture_assert_int_not_equal_goto(tctx, smbfp, -1, ok, done, "Failed to open dst file\n"); + + buffer = talloc_array(tctx, char, maxwrite); + torture_assert_not_null_goto(tctx, buffer, ok, done, "Failed to allocate buffer\n"); + + total_read = 0; + + while (!feof(fp)) { + nread = fread(buffer, 1, maxwrite, fp); + if (ferror(fp)) { + torture_warning(tctx, "Error reading file [%s]\n", fpath); + continue; + } + + nwrote = smbcli_write(dinfo->cli->tree, smbfp, 0, buffer, total_read, nread); + if (nwrote != nread) { + torture_warning(tctx, "Not all data in stream written!\n"); + } + + total_read += nread; + } + + fclose(fp); + smbcli_close(dinfo->cli->tree, smbfp); +done: + + TALLOC_FREE(buffer); + return ok; +} + +/* Callback function provided to tftw() to + * copy driver files to smb share */ +static int copy_driver_files(TALLOC_CTX *tctx, + const char *fpath, + const struct stat *sb, + enum tftw_flags_e flag, + void *userdata) +{ + char *dst_fpath = NULL; + struct test_driver_info *dinfo = userdata; + char *path = NULL; + NTSTATUS status; + bool ok = true; + + path = talloc_strdup(tctx, fpath + dinfo->driver_path_len); + torture_assert_not_null_goto(tctx, path, ok, done, "Cannot allocate memory"); + + string_replace(path, '/', '\\'); + + dst_fpath = talloc_asprintf(tctx, "%s%s", dinfo->print_upload_guid_dir, path); + torture_assert_not_null_goto(tctx, dst_fpath, ok, done, "Cannot allocate memory"); + + switch (flag) { + case TFTW_FLAG_FILE: + ok = smb_copy_files(tctx, fpath, dst_fpath, dinfo); + torture_assert_goto(tctx, ok, ok, done, "Failed to copy files over smb"); + break; + case TFTW_FLAG_DIR: + status = smbcli_mkdir(dinfo->cli->tree, dst_fpath); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to create directories"); + break; + case TFTW_FLAG_SLINK: + case TFTW_FLAG_DNR: + case TFTW_FLAG_NSTAT: + case TFTW_FLAG_SPEC: + case TFTW_FLAG_DP: + case TFTW_FLAG_SLN: + torture_warning(tctx, "WARN: Unhandled typeflag [%s]\n", fpath); + break; + } + +done: + TALLOC_FREE(path); + TALLOC_FREE(dst_fpath); + + if (ok == true) { + return 0; + } else { + return 1; + } +} + +static bool test_get_driver_torture_options(struct torture_context *tctx, + const char **_local_driver_path, + const char **_inf_file, + const char **_driver_name, + const char **_driver_arch, + const char **_core_driver_inf) +{ + const char *local_driver_path = NULL; + const char *inf_file = NULL; + const char *driver_name = NULL; + const char *driver_arch = NULL; + const char *core_driver_inf = NULL; + const char *arches_list[] = { + SPOOLSS_ARCHITECTURE_x64, + SPOOLSS_ARCHITECTURE_NT_X86, + SPOOLSS_ARCHITECTURE_IA_64, + SPOOLSS_ARCHITECTURE_ARM, + SPOOLSS_ARCHITECTURE_4_0, + NULL, + }; + const char **p; + bool valid = false; + bool ok = true; + + local_driver_path = torture_setting_string(tctx, "driver_path", NULL); + if (local_driver_path == NULL) { + torture_fail(tctx, + "option --option=torture:driver_path=" + "/full/path/to/local/driver/dir\n"); + } + + inf_file = torture_setting_string(tctx, "inf_file", NULL); + if (inf_file == NULL) { + torture_fail(tctx, + "option --option=torture:inf_file=" + "filename.inf\n"); + } + + driver_name = torture_setting_string(tctx, "driver_name", NULL); + if (driver_name == NULL) { + torture_fail(tctx, + "option --option=torture:driver_name=" + "driver name\n"); + } + + driver_arch = torture_setting_string(tctx, "driver_arch", NULL); + if (driver_arch == NULL) { + torture_fail(tctx, + "option --option=torture:driver_arch=" + "driver arch\n"); + } + + core_driver_inf = torture_setting_string(tctx, "core_driver_inf", NULL); + + for (p = arches_list; *p != NULL; p++) { + if (strequal(*p, driver_arch) == 0) { + valid = true; + break; + } + } + torture_assert_goto(tctx, valid, ok, done, "Invalid driver arch provided"); + + *_local_driver_path = local_driver_path; + *_inf_file = inf_file; + *_driver_name = driver_name; + *_driver_arch = driver_arch; + *_core_driver_inf = core_driver_inf; +done: + return ok; +} + + +static bool test_get_misc_driver_info(struct torture_context *tctx, + struct test_driver_info *dinfo, + const char **_abs_inf_path, + size_t *_driver_path_len) +{ + const char *abs_inf_path; + size_t driver_path_len; + bool ok = true; + + driver_path_len = strlen(dinfo->local_driver_path); + torture_assert_int_not_equal_goto(tctx, driver_path_len, 0, ok, done, "driver path length is 0"); + + abs_inf_path = talloc_asprintf(tctx, "%s/%s", dinfo->local_driver_path, dinfo->inf_file); + torture_assert_not_null_goto(tctx, abs_inf_path, ok, done, "Cannot allocate memory"); + + *_abs_inf_path = abs_inf_path; + *_driver_path_len = driver_path_len; +done: + + return ok; +} + +/* Uninstall the previously installed print driver */ +static bool test_uninstall_printer_driver(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx) +{ + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct winspool_AsyncDeletePrinterDriverEx r; + bool ok = true; + NTSTATUS status; + + r.in.pName = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + r.in.pDriverName = talloc_strdup(tctx, ctx->dinfo->driver_name); + torture_assert_not_null_goto(tctx, r.in.pDriverName, ok, done, "Cannot allocate memory"); + + r.in.pEnvironment = SPOOLSS_ARCHITECTURE_x64; + + r.in.dwDeleteFlag = 0; + r.in.dwVersionNum = 0; + + status = dcerpc_winspool_AsyncDeletePrinterDriverEx_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncDeletePrinterDriverEx failed"); + + torture_assert_werr_ok(tctx, r.out.result, "AsyncDeletePrinterDriverEx failed"); +done: + + return ok; +} + +/* Remove the leftover print driver package files from the driver store */ +static bool test_remove_driver_package(struct torture_context *tctx, + struct test_iremotewinspool_context *ctx) +{ + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct winspool_AsyncDeletePrinterDriverPackage r; + bool ok = true; + NTSTATUS status; + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_assert_not_null_goto(tctx, r.in.pszServer, ok, done, "Cannot allocate memory"); + + r.in.pszInfPath = ctx->dinfo->uploaded_inf_path; + + r.in.pszEnvironment = SPOOLSS_ARCHITECTURE_x64; + + status = dcerpc_winspool_AsyncDeletePrinterDriverPackage_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncDeletePrinterPackage failed"); + + torture_assert_hresult_ok(tctx, r.out.result, "AsyncDeletePrinterDriverPackage failed"); +done: + + return ok; +} + +static bool test_winreg_iremotewinspool_openhklm(struct torture_context *tctx, + struct dcerpc_binding_handle *winreg_bh, + struct policy_handle *_hklm_handle) +{ + struct winreg_OpenHKLM r; + NTSTATUS status; + bool ok = true; + + r.in.system_name = NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = _hklm_handle; + + status = dcerpc_winreg_OpenHKLM_r(winreg_bh, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to Open HKLM"); + + torture_assert_werr_ok(tctx, r.out.result, "Failed to Open HKLM"); +done: + + return ok; +} + +static bool test_winreg_iremotewinspool_openkey(struct torture_context *tctx, + struct dcerpc_binding_handle *winreg_bh, + struct policy_handle *hklm_handle, + const char *keyname, + struct policy_handle *_key_handle) +{ + struct winreg_OpenKey r; + NTSTATUS status; + bool ok = true; + + r.in.parent_handle = hklm_handle; + init_winreg_String(&r.in.keyname, keyname); + r.in.options = REG_OPTION_NON_VOLATILE; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = _key_handle; + + status = dcerpc_winreg_OpenKey_r(winreg_bh, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "OpenKey failed"); + + torture_assert_werr_ok(tctx, r.out.result, "OpenKey failed"); +done: + + return ok; +} + +static bool test_winreg_iremotewinspool_queryvalue(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *key_handle, + const char *value_name, + const char **_valuestr) +{ + struct winreg_QueryValue r; + enum winreg_Type type = REG_NONE; + struct winreg_String valuename; + DATA_BLOB blob; + const char *str; + uint32_t data_size = 0; + uint32_t data_length = 0; + uint8_t *data = NULL; + NTSTATUS status; + bool ok = true; + + init_winreg_String(&valuename, value_name); + + data = talloc_zero_array(tctx, uint8_t, 0); + + r.in.handle = key_handle; + r.in.value_name = &valuename; + r.in.type = &type; + r.in.data_size = &data_size; + r.in.data_length = &data_length; + r.in.data = data; + + r.out.type = &type; + r.out.data = data; + r.out.data_size = &data_size; + r.out.data_length = &data_length; + + status = dcerpc_winreg_QueryValue_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "winreg_QueryValue failure"); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), "QueryValue failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + *r.in.data_size = *r.out.data_size; + data = talloc_zero_array(tctx, uint8_t, *r.in.data_size); + r.in.data = data; + r.out.data = data; + status = dcerpc_winreg_QueryValue_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "QueryValue failed"); + } + torture_assert_werr_ok(tctx, r.out.result, "QueryValue failed"); + + torture_assert_int_equal_goto(tctx, *r.out.type, REG_SZ, ok, done, "unexpected type"); + blob = data_blob(r.out.data, *r.out.data_size); + str = reg_val_data_string(tctx, REG_SZ, blob); + + *_valuestr = str; +done: + + return ok; +} + +/* Validate the installed driver subkey exists, and the InfPath + * value matches the pszDestInfPath from test_UploadPrinterDriverPackage */ +static bool test_winreg_validate_driver(struct torture_context *tctx, + struct dcerpc_pipe *winreg_pipe, + struct test_driver_info *dinfo) +{ + struct policy_handle hklm_handle; + struct policy_handle key_handle; + char *driver_key = NULL; + const char *val_name = NULL; + const char *val_str = NULL; + bool ok = true; + + struct dcerpc_binding_handle *winreg_bh; + struct spoolss_AddDriverInfo8 *parsed_dinfo; + + winreg_bh = winreg_pipe->binding_handle; + parsed_dinfo = dinfo->info; + + /* OpenHKLM */ + ok = test_winreg_iremotewinspool_openhklm(tctx, winreg_bh, &hklm_handle); + torture_assert_goto(tctx, ok, ok, done, "Failed to perform winreg OpenHKLM"); + + /* Open registry subkey for the installed print driver */ + driver_key = talloc_asprintf(tctx, "%s\\Environments\\%s\\Drivers\\Version-%d\\%s", + REG_DRIVER_CONTROL_KEY, + parsed_dinfo->architecture, + parsed_dinfo->version, + parsed_dinfo->driver_name); + torture_assert_not_null_goto(tctx, driver_key, ok, done, "Cannot allocate driver_key string"); + ok = test_winreg_iremotewinspool_openkey(tctx, winreg_bh, &hklm_handle, + driver_key, + &key_handle); + torture_assert_goto(tctx, ok, ok, done, "Failed to perform winreg OpenKey"); + + /* Read infpath value and validate this matches what was uploaded */ + val_name = "InfPath"; + ok = test_winreg_iremotewinspool_queryvalue(tctx, winreg_bh, &key_handle, val_name, + &val_str); + torture_assert_goto(tctx, ok, ok, done, "QueryValue failed"); + + torture_assert_casestr_equal(tctx, val_str, + dinfo->uploaded_inf_path, + "InfPath does not match uploaded inf"); +done: + + return ok; +} + +static bool test_init_iremotewinspool_conn(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + struct dcerpc_binding *binding = {0}; + bool ok = true; + NTSTATUS status; + + status = GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &t->object_uuid); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "failed to parse GUID"); + + status = torture_rpc_binding(tctx, &binding); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "failed to retrieve torture binding"); + + status = dcerpc_binding_set_object(binding, t->object_uuid); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "failed to set object_uuid"); + + status = torture_rpc_connection_with_binding(tctx, binding, &t->iremotewinspool_pipe, + &ndr_table_iremotewinspool); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Error connecting to server"); + +done: + + return ok; + +} + +static bool test_init_iremotewinspool_openprinter(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + struct spoolss_UserLevel1 client_info = {0}; + char *printer_name = NULL; + bool ok = true; + + printer_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(t->iremotewinspool_pipe)); + torture_assert_not_null_goto(tctx, printer_name, ok, done, "Cannot allocate memory"); + + client_info = test_get_client_info(tctx, WIN_7, 3, SPOOLSS_MINOR_VERSION_0, + "testclient_machine", "testclient_user"); + + ok = test_AsyncOpenPrinter_byprinter(tctx, t, t->iremotewinspool_pipe, printer_name, + client_info, &t->server_handle); + torture_assert_goto(tctx, ok, ok, done, "failed to open printserver"); + + ok = test_get_environment(tctx, t->iremotewinspool_pipe->binding_handle, + &t->server_handle, &t->environment); + torture_assert_goto(tctx, ok, ok, done, "failed to get environment"); + +done: + TALLOC_FREE(printer_name); + + return ok; +} + +static bool test_init_driver_info(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + bool ok = true; + const char *abs_inf_path; + struct test_driver_info *drv_info = {0}; + + drv_info = talloc_zero(tctx, struct test_driver_info); + torture_assert_not_null_goto(tctx, drv_info, ok, done, "Cannot allocate memory"); + + t->dinfo = drv_info; + + ok = test_get_driver_torture_options(tctx, + &drv_info->local_driver_path, + &drv_info->inf_file, + &drv_info->driver_name, + &drv_info->driver_arch, + &drv_info->core_driver_inf); + torture_assert_goto(tctx, ok, ok, done, "Failed to get driver torture options"); + + ok = test_get_misc_driver_info(tctx, drv_info, + &abs_inf_path, + &drv_info->driver_path_len); + torture_assert_goto(tctx, ok, ok, done, "Failed to get misc driver info"); + + ok = parse_inf_driver(tctx, drv_info->driver_name, abs_inf_path, drv_info->driver_arch, + drv_info->core_driver_inf, &drv_info->info); + torture_assert_goto(tctx, ok, ok, done, "Failed to parse inf driver"); + + /* Ensure that we are trying to install the correct device class: + * https://docs.microsoft.com/en-us/windows-hardware/drivers/install/system-defined-device-setup-classes-available-to-vendors + */ + if (!(drv_info->info->printer_driver_attributes & PRINTER_DRIVER_CLASS)) { + ok = false; + torture_fail_goto(tctx, done, "Inf file Class value must be Printer"); + } +done: + return ok; + +} + +static bool test_init_server_and_share_info(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + struct GUID guid; + bool ok = true; + + t->dinfo->server_name = talloc_asprintf(tctx, "%s", dcerpc_server_name(t->iremotewinspool_pipe)); + torture_assert_not_null_goto(tctx, t->dinfo->server_name, ok, done, "Cannot allocate memory"); + + t->dinfo->share_name = talloc_strdup(tctx, "print$"); + torture_assert_not_null_goto(tctx, t->dinfo->share_name, ok, done, "Cannot allocate memory"); + + guid = GUID_random(); + t->dinfo->print_upload_guid_dir = GUID_string2(tctx, &guid); +done: + return ok; +} + + +static bool torture_rpc_iremotewinspool_drv_setup_common(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + bool ok = true; + int ret = 0; + + ok = test_init_driver_info(tctx, t); + torture_assert_goto(tctx, ok, ok, done, "failed to init driver info"); + + ok = test_init_iremotewinspool_conn(tctx, t); + torture_assert_goto(tctx, ok, ok, done, "failed to init iremotewinspool conn"); + + ok = test_init_iremotewinspool_openprinter(tctx, t); + torture_assert_goto(tctx, ok, ok, done, "failed to init iremotewinspool openprinter"); + + ok = test_init_server_and_share_info(tctx, t); + torture_assert_goto(tctx, ok, ok, done, "failed to init server and share info"); + + ret = smb_connect_print_share(tctx, t->dinfo->server_name, t->dinfo->share_name, &t->dinfo->cli); + torture_assert_goto(tctx, ret, ok, done, "Failed to connect to print share"); + +done: + + return ok; +} + +static bool torture_rpc_iremotewinspool_drv_setup(struct torture_context *tctx, + void **data) +{ + struct test_iremotewinspool_context *t; + + *data = t = talloc_zero(tctx, struct test_iremotewinspool_context); + + return torture_rpc_iremotewinspool_drv_setup_common(tctx, t); +} + +static bool torture_rpc_iremotewinspool_drv_teardown_common(struct torture_context *tctx, + struct test_iremotewinspool_context *t) +{ + smbcli_deltree(t->dinfo->cli->tree, t->dinfo->print_upload_guid_dir); + smb_raw_exit(t->dinfo->cli->session); + + test_uninstall_printer_driver(tctx, t); + test_remove_driver_package(tctx, t); + + test_AsyncClosePrinter_byhandle(tctx, t, t->iremotewinspool_pipe, &t->server_handle); + + return true; +} + +static bool torture_rpc_iremotewinspool_drv_teardown(struct torture_context *tctx, + void *data) +{ + struct test_iremotewinspool_context *t = talloc_get_type(data, struct test_iremotewinspool_context); + bool ret; + + ret = torture_rpc_iremotewinspool_drv_teardown_common(tctx, t); + talloc_free(t); + + return ret; +} + +/* Creates {GUID} directory inside //server/print$ then copies driver files + * and directories from torture option driver_path to this directory over smb */ +static bool test_CopyDriverFiles(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + bool ret = false; + bool ok = true; + NTSTATUS status; + + status = smbcli_mkdir(ctx->dinfo->cli->tree, ctx->dinfo->print_upload_guid_dir); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to create upload directory"); + + /* Walk the provided torture option driver_path file tree, creating the directory heirarchy and + * copying all files to print$/{GUID}/ share */ + ret = tftw(tctx, ctx->dinfo->local_driver_path, copy_driver_files, TFTW_MAX_DEPTH, ctx->dinfo); + torture_assert_int_equal_goto(tctx, ret, 0, ok, done, "Failed to copy driver files to print$/{GUID}/ dir"); + +done: + + return ok; +} + +/* + * Upload print driver package files and inf file, preparing the print server + * for driver installation + */ +static bool test_UploadPrinterDriverPackage(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct spoolss_AddDriverInfo8 *parsed_dinfo; + struct winspool_AsyncUploadPrinterDriverPackage r; + uint32_t pcchDestInfPath = 0; + NTSTATUS status; + bool ok = true; + + parsed_dinfo = ctx->dinfo->info; + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_assert_not_null_goto(tctx, r.in.pszServer, ok, done, "Cannot allocate memory"); + + r.in.pszInfPath = talloc_asprintf(tctx, "\\\\%s\\%s\\%s\\%s", ctx->dinfo->server_name, + ctx->dinfo->share_name, + ctx->dinfo->print_upload_guid_dir, + ctx->dinfo->inf_file); + torture_assert_not_null_goto(tctx, r.in.pszInfPath, ok, done, "Cannot allocate memory"); + + r.in.pszEnvironment = parsed_dinfo->architecture; + /* Upload driver package files even if the driver package is already present + * on the print server */ + r.in.dwFlags = UPDP_UPLOAD_ALWAYS; + pcchDestInfPath = 260; + r.in.pszDestInfPath = NULL; + r.in.pcchDestInfPath = &pcchDestInfPath; + r.out.pszDestInfPath = NULL; + r.out.pcchDestInfPath = &pcchDestInfPath; + + r.in.pszDestInfPath = talloc_zero(tctx, const char); + torture_assert_not_null_goto(tctx, r.in.pszDestInfPath, ok, done, "Cannot allocate memory"); + r.out.pszDestInfPath = talloc_zero(tctx, const char); + torture_assert_not_null_goto(tctx, r.out.pszDestInfPath, ok, done, "Cannot allocate memory"); + + status = dcerpc_winspool_AsyncUploadPrinterDriverPackage_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncUploadPrinterDriverPackage failed"); + + torture_assert_hresult_ok(tctx, r.out.result, "AsyncUploadPrinterDriverPackage failed"); + + ctx->dinfo->uploaded_inf_path = talloc_strdup(tctx, r.out.pszDestInfPath); + torture_assert_not_null_goto(tctx, ctx->dinfo->uploaded_inf_path, ok, done, "Cannot allocate memory"); + +done: + + return ok; +} + +/* Install the driver that was successfully uploaded to the printer driver + * store, note that Windows validates the pszDriverName as mentioned below */ +static bool test_InstallPrinterDriverFromPackage(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *p = ctx->iremotewinspool_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + char *abs_inf_path = NULL; + struct spoolss_AddDriverInfo8 *parsed_dinfo; + struct winspool_AsyncInstallPrinterDriverFromPackage r; + bool ok = true; + NTSTATUS status; + + parsed_dinfo = ctx->dinfo->info; + + r.in.pszServer = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_assert_not_null_goto(tctx, r.in.pszServer, ok, done, "Cannot allocate memory"); + + /* output string(pszDestInfPath) from test_UploadPrinterDriverPackage() */ + r.in.pszInfPath = talloc_strdup(tctx, ctx->dinfo->uploaded_inf_path); + torture_assert_not_null_goto(tctx, r.in.pszInfPath, ok, done, "Cannot allocate memory"); + + abs_inf_path = talloc_asprintf(tctx, "%s/%s", ctx->dinfo->local_driver_path, ctx->dinfo->inf_file); + torture_assert_not_null_goto(tctx, abs_inf_path, ok, done, "Cannot allocate memory"); + + r.in.pszEnvironment = parsed_dinfo->architecture; + torture_assert_not_null_goto(tctx, r.in.pszEnvironment, ok, done, "Cannot allocate memory"); + + /* Windows validates the print driver name by checking the pszDriverName input against the inf file: + * 1) "DriverName" value + * 2) "CompatName" value + * 3) left-hand-side value under the [Model] section + * otherwise ERROR_UNKNOWN_PRINTER_DRIVER is returned */ + r.in.pszDriverName = parsed_dinfo->driver_name; + torture_assert_not_null_goto(tctx, r.in.pszDriverName, ok, done, "Cannot allocate memory"); + + /* All files should be installed, even if doing so would overwrite some newer + * versions */ + r.in.dwFlags = IPDFP_COPY_ALL_FILES; + + status = dcerpc_winspool_AsyncInstallPrinterDriverFromPackage_r(b, tctx, &r); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "AsyncInstallPrinterDriverFromPackage failed"); + + torture_assert_hresult_ok(tctx, r.out.result, "AsyncInstallPrinterDriverFromPackage failed"); +done: + TALLOC_FREE(abs_inf_path); + + return ok; +} + +/* Check the registry to validate the print driver installed successfully */ +static bool test_ValidatePrinterDriverInstalled(struct torture_context *tctx, + void *private_data) +{ + struct test_iremotewinspool_context *ctx = + talloc_get_type_abort(private_data, struct test_iremotewinspool_context); + + struct dcerpc_pipe *winreg_pipe = NULL; + NTSTATUS status; + bool ok = true; + + /* winreg is not available over ncacn_ip_tcp */ + status = torture_rpc_connection_transport(tctx, &winreg_pipe, &ndr_table_winreg, NCACN_NP, 0, 0); + if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_NOT_AVAILABLE)) { + /* retry */ + status = torture_rpc_connection_transport(tctx, &winreg_pipe, &ndr_table_winreg, NCACN_NP, 0, 0); + } + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, "Failed to connect to winreg"); + + ok = test_winreg_validate_driver(tctx, winreg_pipe, ctx->dinfo); + torture_assert_goto(tctx, ok, ok, done, "Failed to validate driver with winreg"); + +done: + TALLOC_FREE(winreg_pipe); + + return ok; +} + +struct torture_suite *torture_rpc_iremotewinspool_drv(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "iremotewinspool_driver"); + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "drivers"); + + torture_tcase_set_fixture(tcase, + torture_rpc_iremotewinspool_drv_setup, + torture_rpc_iremotewinspool_drv_teardown); + + torture_tcase_add_simple_test(tcase, "CopyDriverFiles", test_CopyDriverFiles); + torture_tcase_add_simple_test(tcase, "UploadPrinterDriverPackage", test_UploadPrinterDriverPackage); + torture_tcase_add_simple_test(tcase, "InstallPrinterDriverFromPackage", test_InstallPrinterDriverFromPackage); + torture_tcase_add_simple_test(tcase, "ValidatePrinterDriverInstalled", test_ValidatePrinterDriverInstalled); + + return suite; +} diff --git a/source4/torture/rpc/join.c b/source4/torture/rpc/join.c new file mode 100644 index 0000000..6e0afca --- /dev/null +++ b/source4/torture/rpc/join.c @@ -0,0 +1,86 @@ +#include "includes.h" +#include "libcli/libcli.h" + +#include "torture/rpc/torture_rpc.h" + +#include "libcli/resolve/resolve.h" +#include "param/param.h" + +#define TORTURE_NETBIOS_NAME "smbtorturejoin" + + +bool torture_rpc_join(struct torture_context *torture) +{ + NTSTATUS status; + struct test_join *tj; + struct cli_credentials *machine_account; + struct smbcli_state *cli; + const char *host = torture_setting_string(torture, "host", NULL); + struct smbcli_options options; + struct smbcli_session_options session_options; + + /* Join domain as a member server. */ + tj = torture_join_domain(torture, + TORTURE_NETBIOS_NAME, + ACB_WSTRUST, + &machine_account); + + if (!tj) { + DEBUG(0, ("%s failed to join domain as workstation\n", + TORTURE_NETBIOS_NAME)); + return false; + } + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(tj, &cli, host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + machine_account, + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("%s failed to connect to IPC$ with workstation credentials\n", + TORTURE_NETBIOS_NAME)); + return false; + } + smbcli_tdis(cli); + + /* Leave domain. */ + torture_leave_domain(torture, tj); + + /* Join domain as a domain controller. */ + tj = torture_join_domain(torture, TORTURE_NETBIOS_NAME, + ACB_SVRTRUST, + &machine_account); + if (!tj) { + DEBUG(0, ("%s failed to join domain as domain controller\n", + TORTURE_NETBIOS_NAME)); + return false; + } + + status = smbcli_full_connection(tj, &cli, host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + machine_account, + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("%s failed to connect to IPC$ with workstation credentials\n", + TORTURE_NETBIOS_NAME)); + return false; + } + + smbcli_tdis(cli); + + /* Leave domain. */ + torture_leave_domain(torture, tj); + + return true; +} + diff --git a/source4/torture/rpc/lsa.c b/source4/torture/rpc/lsa.c new file mode 100644 index 0000000..d225468 --- /dev/null +++ b/source4/torture/rpc/lsa.c @@ -0,0 +1,5466 @@ +/* + Unix SMB/CIFS implementation. + test suite for lsa rpc operations + + Copyright (C) Andrew Tridgell 2003 + 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 "torture/torture.h" +#include "libcli/cldap/cldap.h" +#include "../lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "lib/events/events.h" +#include "libcli/security/security.h" +#include "libcli/auth/libcli_auth.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "source4/auth/kerberos/kerberos.h" +#include "source4/auth/kerberos/kerberos_util.h" +#include "lib/util/util_net.h" +#include "libcli/resolve/resolve.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#define TEST_MACHINENAME "lsatestmach" +#define TRUSTPW "12345678" + +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; +} + +static bool test_OpenPolicy(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct lsa_ObjectAttribute attr; + struct policy_handle handle; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy r; + uint16_t system_name = '\\'; + + torture_comment(tctx, "\nTesting OpenPolicy\n"); + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = &system_name; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenPolicy_r(b, tctx, &r), + "OpenPolicy failed"); + + torture_assert_ntstatus_ok(tctx, + r.out.result, + "OpenPolicy failed"); + + return true; +} + +static bool test_OpenPolicy_fail(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct lsa_ObjectAttribute attr; + struct policy_handle handle; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy r; + uint16_t system_name = '\\'; + NTSTATUS status; + + torture_comment(tctx, "\nTesting OpenPolicy_fail\n"); + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = &system_name; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "OpenPolicy correctly returned with " + "status: %s\n", + nt_errstr(status)); + return true; + } + + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_ACCESS_DENIED, + "OpenPolicy return value should " + "be ACCESS_DENIED"); + return true; + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + torture_comment(tctx, + "OpenPolicy correctly returned with " + "result: %s\n", + nt_errstr(r.out.result)); + return true; + } + } + + torture_assert_ntstatus_equal(tctx, + r.out.result, + NT_STATUS_OK, + "OpenPolicy return value should be " + "ACCESS_DENIED"); + + return false; +} + + +bool test_lsa_OpenPolicy2_ex(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle **handle, + NTSTATUS expected_status, + NTSTATUS expected_status2) +{ + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + NTSTATUS status; + + torture_comment(tctx, "\nTesting OpenPolicy2\n"); + + *handle = talloc(tctx, struct policy_handle); + torture_assert(tctx, *handle != NULL, "talloc(tctx, struct policy_handle)"); + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = *handle; + + status = dcerpc_lsa_OpenPolicy2_r(b, tctx, &r); + + /* Allow two possible failure status codes */ + if (!NT_STATUS_EQUAL(status, expected_status2)) { + torture_assert_ntstatus_equal(tctx, status, + expected_status, + "OpenPolicy2 failed"); + } + if (!NT_STATUS_IS_OK(expected_status) || + !NT_STATUS_IS_OK(expected_status2)) { + return true; + } + + torture_assert_ntstatus_ok(tctx, + r.out.result, + "OpenPolicy2 failed"); + + return true; +} + + +bool test_lsa_OpenPolicy2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle **handle) +{ + return test_lsa_OpenPolicy2_ex(b, tctx, handle, + NT_STATUS_OK, NT_STATUS_OK); +} + +static bool test_OpenPolicy2_fail(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct lsa_ObjectAttribute attr; + struct policy_handle handle; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + NTSTATUS status; + + torture_comment(tctx, "\nTesting OpenPolicy2_fail\n"); + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy2_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "OpenPolicy2 correctly returned with " + "status: %s\n", + nt_errstr(status)); + return true; + } + + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_ACCESS_DENIED, + "OpenPolicy2 return value should " + "be ACCESS_DENIED"); + return true; + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + torture_comment(tctx, + "OpenPolicy2 correctly returned with " + "result: %s\n", + nt_errstr(r.out.result)); + return true; + } + + torture_fail(tctx, + "OpenPolicy2 return value should be " + "ACCESS_DENIED or RPC_PROTSEQ_NOT_SUPPORTED"); + + return false; +} + +static bool test_LookupNames(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level, + struct lsa_TransNameArray *tnames) +{ + struct lsa_LookupNames r; + struct lsa_TransSidArray sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String *names; + uint32_t count = 0; + int i; + uint32_t *input_idx; + + torture_comment(tctx, "\nTesting LookupNames with %d names\n", tnames->count); + + sids.count = 0; + sids.sids = NULL; + + + r.in.num_names = 0; + + input_idx = talloc_array(tctx, uint32_t, tnames->count); + names = talloc_array(tctx, struct lsa_String, tnames->count); + + for (i=0;i<tnames->count;i++) { + if (tnames->names[i].sid_type != SID_NAME_UNKNOWN) { + init_lsa_String(&names[r.in.num_names], tnames->names[i].name.string); + input_idx[r.in.num_names] = i; + r.in.num_names++; + } + } + + r.in.handle = handle; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames_r(b, tctx, &r), + "LookupNames failed"); + if (NT_STATUS_EQUAL(r.out.result, STATUS_SOME_UNMAPPED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_NONE_MAPPED)) { + for (i=0;i< r.in.num_names;i++) { + if (i < count && sids.sids[i].sid_type == SID_NAME_UNKNOWN) { + torture_comment(tctx, "LookupName of %s was unmapped\n", + tnames->names[i].name.string); + } else if (i >=count) { + torture_comment(tctx, "LookupName of %s failed to return a result\n", + tnames->names[i].name.string); + } + } + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupNames failed"); + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupNames failed"); + } + + for (i=0;i< r.in.num_names;i++) { + torture_assert(tctx, (i < count), + talloc_asprintf(tctx, + "LookupName of %s failed to return a result\n", + tnames->names[input_idx[i]].name.string)); + + torture_assert_int_equal(tctx, + sids.sids[i].sid_type, + tnames->names[input_idx[i]].sid_type, + talloc_asprintf(tctx, + "LookupName of %s got unexpected name type: %s\n", + tnames->names[input_idx[i]].name.string, + sid_type_lookup(sids.sids[i].sid_type))); + if (sids.sids[i].sid_type != SID_NAME_DOMAIN) { + continue; + } + torture_assert_int_equal(tctx, + sids.sids[i].rid, + UINT32_MAX, + talloc_asprintf(tctx, + "LookupName of %s got unexpected rid: %d\n", + tnames->names[input_idx[i]].name.string, + sids.sids[i].rid)); + } + + return true; +} + +static bool test_LookupNames_bogus(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level) +{ + struct lsa_LookupNames r; + struct lsa_TransSidArray sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String names[1]; + uint32_t count = 0; + + torture_comment(tctx, "\nTesting LookupNames with bogus name\n"); + + sids.count = 0; + sids.sids = NULL; + + init_lsa_String(&names[0], "NT AUTHORITY\\BOGUS"); + + r.in.handle = handle; + r.in.num_names = 1; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames_r(b, tctx, &r), + "LookupNames bogus failed"); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_NONE_MAPPED)) { + torture_comment(tctx, "LookupNames failed - %s\n", + nt_errstr(r.out.result)); + return false; + } + + torture_comment(tctx, "\n"); + + return true; +} + +static bool test_LookupNames_NULL(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level) +{ + struct lsa_LookupNames r; + struct lsa_TransSidArray sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String names[1]; + uint32_t count = 0; + + torture_comment(tctx, "\nTesting LookupNames with NULL name\n"); + + sids.count = 0; + sids.sids = NULL; + + names[0].string = NULL; + + r.in.handle = handle; + r.in.num_names = 1; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + /* 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 + */ + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames_r(b, tctx, &r), + "LookupNames with NULL name failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupNames with NULL name failed"); + + torture_comment(tctx, "\n"); + + return true; +} + +static bool test_LookupNames_wellknown(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level) +{ + struct lsa_TranslatedName name; + struct lsa_TransNameArray tnames; + bool ret = true; + + torture_comment(tctx, "Testing LookupNames with well known names\n"); + + tnames.names = &name; + tnames.count = 1; + name.name.string = "NT AUTHORITY\\SYSTEM"; + name.sid_type = SID_NAME_WKN_GRP; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "NT AUTHORITY\\ANONYMOUS LOGON"; + name.sid_type = SID_NAME_WKN_GRP; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "NT AUTHORITY\\Authenticated Users"; + name.sid_type = SID_NAME_WKN_GRP; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + +#if 0 + name.name.string = "NT AUTHORITY"; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "NT AUTHORITY\\"; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); +#endif + + name.name.string = "BUILTIN\\"; + name.sid_type = SID_NAME_DOMAIN; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "BUILTIN\\Administrators"; + name.sid_type = SID_NAME_ALIAS; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "SYSTEM"; + name.sid_type = SID_NAME_WKN_GRP; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + + name.name.string = "Everyone"; + name.sid_type = SID_NAME_WKN_GRP; + ret &= test_LookupNames(b, tctx, handle, level, &tnames); + return ret; +} + +static bool test_LookupNames2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level, + struct lsa_TransNameArray2 *tnames, + bool check_result) +{ + struct lsa_LookupNames2 r; + struct lsa_TransSidArray2 sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String *names; + uint32_t *input_idx; + uint32_t count = 0; + int i; + + torture_comment(tctx, "\nTesting LookupNames2 with %d names\n", tnames->count); + + sids.count = 0; + sids.sids = NULL; + + r.in.num_names = 0; + + input_idx = talloc_array(tctx, uint32_t, tnames->count); + names = talloc_array(tctx, struct lsa_String, tnames->count); + + for (i=0;i<tnames->count;i++) { + if (tnames->names[i].sid_type != SID_NAME_UNKNOWN) { + init_lsa_String(&names[r.in.num_names], tnames->names[i].name.string); + input_idx[r.in.num_names] = i; + r.in.num_names++; + } + } + + r.in.handle = handle; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames2_r(b, tctx, &r), + "LookupNames2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LookupNames2 failed"); + + if (check_result) { + torture_assert_int_equal(tctx, count, sids.count, + "unexpected number of results returned"); + if (sids.count > 0) { + torture_assert(tctx, sids.sids, "invalid sid buffer"); + } + } + + torture_comment(tctx, "\n"); + + return true; +} + + +static bool test_LookupNames3(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level, + struct lsa_TransNameArray2 *tnames, + bool check_result) +{ + struct lsa_LookupNames3 r; + struct lsa_TransSidArray3 sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String *names; + uint32_t count = 0; + int i; + uint32_t *input_idx; + + torture_comment(tctx, "\nTesting LookupNames3 with %d names\n", tnames->count); + + sids.count = 0; + sids.sids = NULL; + + r.in.num_names = 0; + + input_idx = talloc_array(tctx, uint32_t, tnames->count); + names = talloc_array(tctx, struct lsa_String, tnames->count); + for (i=0;i<tnames->count;i++) { + if (tnames->names[i].sid_type != SID_NAME_UNKNOWN) { + init_lsa_String(&names[r.in.num_names], tnames->names[i].name.string); + input_idx[r.in.num_names] = i; + r.in.num_names++; + } + } + + r.in.handle = handle; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames3_r(b, tctx, &r), + "LookupNames3 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupNames3 failed"); + + if (check_result) { + torture_assert_int_equal(tctx, count, sids.count, + "unexpected number of results returned"); + if (sids.count > 0) { + torture_assert(tctx, sids.sids, "invalid sid buffer"); + } + } + + torture_comment(tctx, "\n"); + + return true; +} + +static bool test_LookupNames4(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + enum lsa_LookupNamesLevel level, + struct lsa_TransNameArray2 *tnames, + bool check_result) +{ + struct lsa_LookupNames4 r; + struct lsa_TransSidArray3 sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String *names; + uint32_t count = 0; + int i; + uint32_t *input_idx; + + torture_comment(tctx, "\nTesting LookupNames4 with %d names\n", tnames->count); + + sids.count = 0; + sids.sids = NULL; + + r.in.num_names = 0; + + input_idx = talloc_array(tctx, uint32_t, tnames->count); + names = talloc_array(tctx, struct lsa_String, tnames->count); + for (i=0;i<tnames->count;i++) { + if (tnames->names[i].sid_type != SID_NAME_UNKNOWN) { + init_lsa_String(&names[r.in.num_names], tnames->names[i].name.string); + input_idx[r.in.num_names] = i; + r.in.num_names++; + } + } + + r.in.num_names = tnames->count; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupNames4_r(b, tctx, &r), + "LookupNames4 failed"); + + if (!NT_STATUS_IS_OK(r.out.result)) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NONE_MAPPED)) { + torture_comment(tctx, + "LookupNames4 failed: %s - not considered as an error", + nt_errstr(r.out.result)); + + return true; + } + } + torture_assert_ntstatus_ok(tctx, + r.out.result, + "LookupNames4 failed"); + + if (check_result) { + torture_assert_int_equal(tctx, count, sids.count, + "unexpected number of results returned"); + if (sids.count > 0) { + torture_assert(tctx, sids.sids, "invalid sid buffer"); + } + } + + torture_comment(tctx, "\n"); + + return true; +} + +static bool test_LookupNames4_fail(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + enum lsa_LookupNamesLevel level) +{ + struct lsa_LookupNames4 r; + struct lsa_TransSidArray3 sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String *names = NULL; + uint32_t count = 0; + NTSTATUS status; + + torture_comment(tctx, "\nTesting LookupNames4_fail"); + + sids.count = 0; + sids.sids = NULL; + + r.in.num_names = 0; + + r.in.num_names = count; + r.in.names = names; + r.in.sids = &sids; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.count = &count; + r.out.sids = &sids; + r.out.domains = &domains; + + status = dcerpc_lsa_LookupNames4_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "LookupNames4 correctly returned with " + "status: %s\n", + nt_errstr(status)); + return true; + } + + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_ACCESS_DENIED, + "LookupNames4 return value should " + "be ACCESS_DENIED"); + return true; + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + torture_comment(tctx, + "LookupSids3 correctly returned with " + "result: %s\n", + nt_errstr(r.out.result)); + return true; + } + } + + torture_fail(tctx, + "LookupNames4 return value should be " + "ACCESS_DENIED or RPC_PROTSEQ_NOT_SUPPORTED"); + + return false; +} + + +static bool test_LookupSids(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level, + struct lsa_SidArray *sids) +{ + struct lsa_LookupSids r; + struct lsa_TransNameArray names; + struct lsa_RefDomainList *domains = NULL; + uint32_t count = sids->num_sids; + + torture_comment(tctx, "\nTesting LookupSids\n"); + + names.count = 0; + names.names = NULL; + + r.in.handle = handle; + r.in.sids = sids; + r.in.names = &names; + r.in.level = level; + r.in.count = &count; + r.out.count = &count; + r.out.names = &names; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupSids_r(b, tctx, &r), + "LookupSids failed"); + if (!NT_STATUS_EQUAL(r.out.result, STATUS_SOME_UNMAPPED)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupSids failed"); + } + + torture_comment(tctx, "\n"); + + if (!test_LookupNames(b, tctx, handle, level, &names)) { + return false; + } + + return true; +} + + +static bool test_LookupSids2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level, + struct lsa_SidArray *sids) +{ + struct lsa_LookupSids2 r; + struct lsa_TransNameArray2 names; + struct lsa_RefDomainList *domains = NULL; + uint32_t count = sids->num_sids; + + torture_comment(tctx, "\nTesting LookupSids2\n"); + + names.count = 0; + names.names = NULL; + + r.in.handle = handle; + r.in.sids = sids; + r.in.names = &names; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.count = &count; + r.out.names = &names; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupSids2_r(b, tctx, &r), + "LookupSids2 failed"); + if (!NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(r.out.result, STATUS_SOME_UNMAPPED)) { + torture_comment(tctx, "LookupSids2 failed - %s\n", + nt_errstr(r.out.result)); + return false; + } + + torture_comment(tctx, "\n"); + + if (!test_LookupNames2(b, tctx, handle, level, &names, false)) { + return false; + } + + if (!test_LookupNames3(b, tctx, handle, level, &names, false)) { + return false; + } + + return true; +} + +static bool test_LookupSids3(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + enum lsa_LookupNamesLevel level, + struct lsa_SidArray *sids) +{ + struct lsa_LookupSids3 r; + struct lsa_TransNameArray2 names; + struct lsa_RefDomainList *domains = NULL; + uint32_t count = sids->num_sids; + + torture_comment(tctx, "\nTesting LookupSids3\n"); + + names.count = 0; + names.names = NULL; + + r.in.sids = sids; + r.in.names = &names; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.domains = &domains; + r.out.count = &count; + r.out.names = &names; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupSids3_r(b, tctx, &r), + "LookupSids3 failed"); + + if (!NT_STATUS_IS_OK(r.out.result)) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NONE_MAPPED)) { + torture_comment(tctx, + "LookupSids3 failed: %s - not considered as an error", + nt_errstr(r.out.result)); + + return true; + } + + torture_assert_ntstatus_ok(tctx, + r.out.result, + "LookupSids3 failed"); + + return false; + } + + torture_comment(tctx, "\n"); + + if (!test_LookupNames4(b, tctx, level, &names, true)) { + return false; + } + + return true; +} + +static bool test_LookupSids3_fail(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + enum lsa_LookupNamesLevel level, + struct lsa_SidArray *sids) +{ + struct lsa_LookupSids3 r; + struct lsa_TransNameArray2 names; + struct lsa_RefDomainList *domains = NULL; + uint32_t count = sids->num_sids; + NTSTATUS status; + + torture_comment(tctx, "\nTesting LookupSids3\n"); + + names.count = 0; + names.names = NULL; + + r.in.sids = sids; + r.in.names = &names; + r.in.level = level; + r.in.count = &count; + r.in.lookup_options = 0; + r.in.client_revision = 0; + r.out.domains = &domains; + r.out.count = &count; + r.out.names = &names; + + status = dcerpc_lsa_LookupSids3_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "LookupSids3 correctly returned with " + "status: %s\n", + nt_errstr(status)); + return true; + } + + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_ACCESS_DENIED, + "LookupSids3 return value should " + "be ACCESS_DENIED"); + return true; + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + torture_comment(tctx, + "LookupNames4 correctly returned with " + "result: %s\n", + nt_errstr(r.out.result)); + return true; + } + + torture_fail(tctx, + "LookupSids3 return value should be " + "ACCESS_DENIED or RPC_PROTSEQ_NOT_SUPPORTED"); + + return false; +} + +bool test_many_LookupSids(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level) +{ + uint32_t count; + struct lsa_SidArray sids; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(p->binding); + + torture_comment(tctx, "\nTesting LookupSids with lots of SIDs\n"); + + sids.num_sids = 100; + + sids.sids = talloc_array(tctx, struct lsa_SidPtr, sids.num_sids); + + for (i=0; i<sids.num_sids; i++) { + const char *sidstr = "S-1-5-32-545"; + sids.sids[i].sid = dom_sid_parse_talloc(tctx, sidstr); + } + + count = sids.num_sids; + + if (handle) { + struct lsa_LookupSids r; + struct lsa_TransNameArray names; + struct lsa_RefDomainList *domains = NULL; + names.count = 0; + names.names = NULL; + + r.in.handle = handle; + r.in.sids = &sids; + r.in.names = &names; + r.in.level = level; + r.in.count = &names.count; + r.out.count = &count; + r.out.names = &names; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupSids_r(b, tctx, &r), + "LookupSids failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "LookupSids failed - %s\n", + nt_errstr(r.out.result)); + return false; + } + + torture_comment(tctx, "\n"); + + if (!test_LookupNames(b, tctx, handle, level, &names)) { + return false; + } + } + + if (transport == NCACN_NP) { + if (!test_LookupSids3_fail(b, tctx, level, &sids)) { + return false; + } + if (!test_LookupNames4_fail(b, tctx, level)) { + return false; + } + } else if (transport == NCACN_IP_TCP) { + struct lsa_TransNameArray2 names; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + names.count = 0; + names.names = NULL; + + dcerpc_binding_handle_auth_info(p->binding_handle, + &auth_type, &auth_level); + + if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL && + auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) { + if (!test_LookupSids3(b, tctx, level, &sids)) { + return false; + } + if (!test_LookupNames4(b, tctx, level, &names, true)) { + return false; + } + } else { + /* + * If we don't have a secure channel these tests must + * fail with ACCESS_DENIED. + */ + if (!test_LookupSids3_fail(b, tctx, level, &sids)) { + return false; + } + if (!test_LookupNames4_fail(b, tctx, level)) { + return false; + } + } + } + + torture_comment(tctx, "\n"); + + + + return true; +} + +static void lookupsids_cb(struct tevent_req *subreq) +{ + int *replies = (int *)tevent_req_callback_data_void(subreq); + NTSTATUS status; + + status = dcerpc_lsa_LookupSids_r_recv(subreq, subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + printf("lookupsids returned %s\n", nt_errstr(status)); + *replies = -1; + } + + if (*replies >= 0) { + *replies += 1; + } +} + +static bool test_LookupSids_async(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + enum lsa_LookupNamesLevel level) +{ + struct lsa_SidArray sids; + struct lsa_SidPtr sidptr; + uint32_t *count; + struct lsa_TransNameArray *names; + struct lsa_LookupSids *r; + struct lsa_RefDomainList *domains = NULL; + struct tevent_req **req; + int i, replies; + bool ret = true; + const int num_async_requests = 50; + + count = talloc_array(tctx, uint32_t, num_async_requests); + names = talloc_array(tctx, struct lsa_TransNameArray, num_async_requests); + r = talloc_array(tctx, struct lsa_LookupSids, num_async_requests); + + torture_comment(tctx, "\nTesting %d async lookupsids request\n", num_async_requests); + + req = talloc_array(tctx, struct tevent_req *, num_async_requests); + + sids.num_sids = 1; + sids.sids = &sidptr; + sidptr.sid = dom_sid_parse_talloc(tctx, "S-1-5-32-545"); + + replies = 0; + + for (i=0; i<num_async_requests; i++) { + count[i] = 0; + names[i].count = 0; + names[i].names = NULL; + + r[i].in.handle = handle; + r[i].in.sids = &sids; + r[i].in.names = &names[i]; + r[i].in.level = level; + r[i].in.count = &names[i].count; + r[i].out.count = &count[i]; + r[i].out.names = &names[i]; + r[i].out.domains = &domains; + + req[i] = dcerpc_lsa_LookupSids_r_send(tctx, tctx->ev, b, &r[i]); + if (req[i] == NULL) { + ret = false; + break; + } + + tevent_req_set_callback(req[i], lookupsids_cb, &replies); + } + + while (replies >= 0 && replies < num_async_requests) { + tevent_loop_once(tctx->ev); + } + + talloc_free(req); + + if (replies < 0) { + ret = false; + } + + return ret; +} + +static bool test_LookupPrivValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_String *name) +{ + struct lsa_LookupPrivValue r; + struct lsa_LUID luid; + + r.in.handle = handle; + r.in.name = name; + r.out.luid = &luid; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupPrivValue_r(b, tctx, &r), + "LookupPrivValue failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "LookupPrivValue failed"); + + return true; +} + +static bool test_LookupPrivName(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_LUID *luid) +{ + struct lsa_LookupPrivName r; + struct lsa_StringLarge *name = NULL; + + r.in.handle = handle; + r.in.luid = luid; + r.out.name = &name; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupPrivName_r(b, tctx, &r), + "LookupPrivName failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LookupPrivName failed"); + + return true; +} + +static bool test_RemovePrivilegesFromAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct policy_handle *acct_handle, + struct lsa_LUID *luid) +{ + struct lsa_RemovePrivilegesFromAccount r; + struct lsa_PrivilegeSet privs; + bool ret = true; + + torture_comment(tctx, "\nTesting RemovePrivilegesFromAccount\n"); + + r.in.handle = acct_handle; + r.in.remove_all = 0; + r.in.privs = &privs; + + privs.count = 1; + privs.unknown = 0; + privs.set = talloc_array(tctx, struct lsa_LUIDAttribute, 1); + privs.set[0].luid = *luid; + privs.set[0].attribute = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_RemovePrivilegesFromAccount_r(b, tctx, &r), + "RemovePrivilegesFromAccount failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + + struct lsa_LookupPrivName r_name; + struct lsa_StringLarge *name = NULL; + + r_name.in.handle = handle; + r_name.in.luid = luid; + r_name.out.name = &name; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupPrivName_r(b, tctx, &r_name), + "LookupPrivName failed"); + if (!NT_STATUS_IS_OK(r_name.out.result)) { + torture_comment(tctx, "\nLookupPrivName failed - %s\n", + nt_errstr(r_name.out.result)); + return false; + } + /* Windows 2008 does not allow this to be removed */ + if (strcmp("SeAuditPrivilege", name->string) == 0) { + return ret; + } + + torture_comment(tctx, "RemovePrivilegesFromAccount failed to remove %s - %s\n", + name->string, + nt_errstr(r.out.result)); + return false; + } + + return ret; +} + +static bool test_AddPrivilegesToAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *acct_handle, + struct lsa_LUID *luid) +{ + struct lsa_AddPrivilegesToAccount r; + struct lsa_PrivilegeSet privs; + bool ret = true; + + torture_comment(tctx, "\nTesting AddPrivilegesToAccount\n"); + + r.in.handle = acct_handle; + r.in.privs = &privs; + + privs.count = 1; + privs.unknown = 0; + privs.set = talloc_array(tctx, struct lsa_LUIDAttribute, 1); + privs.set[0].luid = *luid; + privs.set[0].attribute = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_AddPrivilegesToAccount_r(b, tctx, &r), + "AddPrivilegesToAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "AddPrivilegesToAccount failed"); + return ret; +} + +static bool test_EnumPrivsAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct policy_handle *acct_handle) +{ + struct lsa_EnumPrivsAccount r; + struct lsa_PrivilegeSet *privs = NULL; + bool ret = true; + + torture_comment(tctx, "\nTesting EnumPrivsAccount\n"); + + r.in.handle = acct_handle; + r.out.privs = &privs; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumPrivsAccount_r(b, tctx, &r), + "EnumPrivsAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "EnumPrivsAccount failed"); + + if (privs && privs->count > 0) { + int i; + for (i=0;i<privs->count;i++) { + test_LookupPrivName(b, tctx, handle, + &privs->set[i].luid); + } + + ret &= test_RemovePrivilegesFromAccount(b, tctx, handle, acct_handle, + &privs->set[0].luid); + ret &= test_AddPrivilegesToAccount(b, tctx, acct_handle, + &privs->set[0].luid); + } + + return ret; +} + +static bool test_GetSystemAccessAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct policy_handle *acct_handle) +{ + uint32_t access_mask; + struct lsa_GetSystemAccessAccount r; + + torture_comment(tctx, "\nTesting GetSystemAccessAccount\n"); + + r.in.handle = acct_handle; + r.out.access_mask = &access_mask; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetSystemAccessAccount_r(b, tctx, &r), + "GetSystemAccessAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "GetSystemAccessAccount failed"); + + if (r.out.access_mask != NULL) { + torture_comment(tctx, "Rights:"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_INTERACTIVE) + torture_comment(tctx, " LSA_POLICY_MODE_INTERACTIVE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_NETWORK) + torture_comment(tctx, " LSA_POLICY_MODE_NETWORK"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_BATCH) + torture_comment(tctx, " LSA_POLICY_MODE_BATCH"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_SERVICE) + torture_comment(tctx, " LSA_POLICY_MODE_SERVICE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_PROXY) + torture_comment(tctx, " LSA_POLICY_MODE_PROXY"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_DENY_INTERACTIVE) + torture_comment(tctx, " LSA_POLICY_MODE_DENY_INTERACTIVE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_DENY_NETWORK) + torture_comment(tctx, " LSA_POLICY_MODE_DENY_NETWORK"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_DENY_BATCH) + torture_comment(tctx, " LSA_POLICY_MODE_DENY_BATCH"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_DENY_SERVICE) + torture_comment(tctx, " LSA_POLICY_MODE_DENY_SERVICE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_REMOTE_INTERACTIVE) + torture_comment(tctx, " LSA_POLICY_MODE_REMOTE_INTERACTIVE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_DENY_REMOTE_INTERACTIVE) + torture_comment(tctx, " LSA_POLICY_MODE_DENY_REMOTE_INTERACTIVE"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_ALL) + torture_comment(tctx, " LSA_POLICY_MODE_ALL"); + if (*(r.out.access_mask) & LSA_POLICY_MODE_ALL_NT4) + torture_comment(tctx, " LSA_POLICY_MODE_ALL_NT4"); + torture_comment(tctx, "\n"); + } + + return true; +} + +static bool test_Delete(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_Delete r; + + torture_comment(tctx, "\nTesting Delete\n"); + + r.in.handle = handle; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_Delete_r(b, tctx, &r), + "Delete failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_SUPPORTED, + "Delete should have failed NT_STATUS_NOT_SUPPORTED"); + + return true; +} + +static bool test_DeleteObject(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_DeleteObject r; + + torture_comment(tctx, "\nTesting DeleteObject\n"); + + r.in.handle = handle; + r.out.handle = handle; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_DeleteObject_r(b, tctx, &r), + "DeleteObject failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "DeleteObject failed"); + + return true; +} + + +static bool test_CreateAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_CreateAccount r; + struct dom_sid2 *newsid; + struct policy_handle acct_handle; + + newsid = dom_sid_parse_talloc(tctx, "S-1-5-12349876-4321-2854"); + + torture_comment(tctx, "\nTesting CreateAccount\n"); + + r.in.handle = handle; + r.in.sid = newsid; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.acct_handle = &acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateAccount_r(b, tctx, &r), + "CreateAccount failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_OBJECT_NAME_COLLISION)) { + struct lsa_OpenAccount r_o; + r_o.in.handle = handle; + r_o.in.sid = newsid; + r_o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r_o.out.acct_handle = &acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenAccount_r(b, tctx, &r_o), + "OpenAccount failed"); + torture_assert_ntstatus_ok(tctx, r_o.out.result, + "OpenAccount failed"); + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, + "CreateAccount failed"); + } + + if (!test_Delete(b, tctx, &acct_handle)) { + return false; + } + + if (!test_DeleteObject(b, tctx, &acct_handle)) { + return false; + } + + return true; +} + +static bool test_DeleteTrustedDomain(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_StringLarge name) +{ + struct lsa_OpenTrustedDomainByName r; + struct policy_handle trustdom_handle; + + r.in.handle = handle; + r.in.name.string = name.string; + r.in.access_mask = SEC_STD_DELETE; + r.out.trustdom_handle = &trustdom_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenTrustedDomainByName_r(b, tctx, &r), + "OpenTrustedDomainByName failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "OpenTrustedDomainByName failed"); + + if (!test_Delete(b, tctx, &trustdom_handle)) { + return false; + } + + if (!test_DeleteObject(b, tctx, &trustdom_handle)) { + return false; + } + + return true; +} + +static bool test_DeleteTrustedDomainBySid(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct dom_sid *sid) +{ + struct lsa_DeleteTrustedDomain r; + + r.in.handle = handle; + r.in.dom_sid = sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_DeleteTrustedDomain_r(b, tctx, &r), + "DeleteTrustedDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "DeleteTrustedDomain failed"); + + return true; +} + + +static bool test_CreateSecret(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_CreateSecret r; + struct lsa_OpenSecret r2; + struct lsa_SetSecret r3; + struct lsa_QuerySecret r4; + struct lsa_SetSecret r5; + struct lsa_QuerySecret r6; + struct lsa_SetSecret r7; + struct lsa_QuerySecret r8; + struct policy_handle sec_handle, sec_handle2, sec_handle3; + struct lsa_DeleteObject d_o; + struct lsa_DATA_BUF buf1; + struct lsa_DATA_BUF_PTR bufp1; + struct lsa_DATA_BUF_PTR bufp2; + DATA_BLOB enc_key; + bool ret = true; + DATA_BLOB session_key; + NTTIME old_mtime, new_mtime; + DATA_BLOB blob1; + const char *secret1 = "abcdef12345699qwerty"; + char *secret2; + const char *secret3 = "ABCDEF12345699QWERTY"; + char *secret4; + const char *secret5 = "NEW-SAMBA4-SECRET"; + char *secret6; + char *secname[2]; + int i; + const int LOCAL = 0; + const int GLOBAL = 1; + struct dcerpc_binding_handle *b = p->binding_handle; + + secname[LOCAL] = talloc_asprintf(tctx, "torturesecret-%u", (unsigned int)random()); + secname[GLOBAL] = talloc_asprintf(tctx, "G$torturesecret-%u", (unsigned int)random()); + + for (i=0; i< 2; i++) { + torture_comment(tctx, "\nTesting CreateSecret of %s\n", secname[i]); + + init_lsa_String(&r.in.name, secname[i]); + + r.in.handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.sec_handle = &sec_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateSecret_r(b, tctx, &r), + "CreateSecret failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "CreateSecret failed"); + + r.in.handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.sec_handle = &sec_handle3; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateSecret_r(b, tctx, &r), + "CreateSecret failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_OBJECT_NAME_COLLISION, + "CreateSecret should have failed OBJECT_NAME_COLLISION"); + + r2.in.handle = handle; + r2.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r2.in.name = r.in.name; + r2.out.sec_handle = &sec_handle2; + + torture_comment(tctx, "Testing OpenSecret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenSecret_r(b, tctx, &r2), + "OpenSecret failed"); + torture_assert_ntstatus_ok(tctx, r2.out.result, + "OpenSecret failed"); + + torture_assert_ntstatus_ok(tctx, dcerpc_fetch_session_key(p, &session_key), + "dcerpc_fetch_session_key failed"); + + enc_key = sess_encrypt_string(secret1, &session_key); + + r3.in.sec_handle = &sec_handle; + r3.in.new_val = &buf1; + r3.in.old_val = NULL; + r3.in.new_val->data = enc_key.data; + r3.in.new_val->length = enc_key.length; + r3.in.new_val->size = enc_key.length; + + torture_comment(tctx, "Testing SetSecret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r3), + "SetSecret failed"); + torture_assert_ntstatus_ok(tctx, r3.out.result, + "SetSecret failed"); + + r3.in.sec_handle = &sec_handle; + r3.in.new_val = &buf1; + r3.in.old_val = NULL; + r3.in.new_val->data = enc_key.data; + r3.in.new_val->length = enc_key.length; + r3.in.new_val->size = enc_key.length; + + /* break the encrypted data */ + enc_key.data[0]++; + + torture_comment(tctx, "Testing SetSecret with broken key\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r3), + "SetSecret failed"); + torture_assert_ntstatus_equal(tctx, r3.out.result, NT_STATUS_UNKNOWN_REVISION, + "SetSecret should have failed UNKNOWN_REVISION"); + + data_blob_free(&enc_key); + + ZERO_STRUCT(new_mtime); + ZERO_STRUCT(old_mtime); + + /* fetch the secret back again */ + r4.in.sec_handle = &sec_handle; + r4.in.new_val = &bufp1; + r4.in.new_mtime = &new_mtime; + r4.in.old_val = NULL; + r4.in.old_mtime = NULL; + + bufp1.buf = NULL; + + torture_comment(tctx, "Testing QuerySecret\n"); + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(b, tctx, &r4), + "QuerySecret failed"); + if (!NT_STATUS_IS_OK(r4.out.result)) { + torture_comment(tctx, "QuerySecret failed - %s\n", nt_errstr(r4.out.result)); + ret = false; + } else { + if (r4.out.new_val == NULL || r4.out.new_val->buf == NULL) { + torture_comment(tctx, "No secret buffer returned\n"); + ret = false; + } else { + blob1.data = r4.out.new_val->buf->data; + blob1.length = r4.out.new_val->buf->size; + + secret2 = sess_decrypt_string(tctx, + &blob1, &session_key); + + if (strcmp(secret1, secret2) != 0) { + torture_comment(tctx, "Returned secret (r4) '%s' doesn't match '%s'\n", + secret2, secret1); + ret = false; + } + } + } + + enc_key = sess_encrypt_string(secret3, &session_key); + + r5.in.sec_handle = &sec_handle; + r5.in.new_val = &buf1; + r5.in.old_val = NULL; + r5.in.new_val->data = enc_key.data; + r5.in.new_val->length = enc_key.length; + r5.in.new_val->size = enc_key.length; + + + smb_msleep(200); + torture_comment(tctx, "Testing SetSecret (existing value should move to old)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r5), + "SetSecret failed"); + if (!NT_STATUS_IS_OK(r5.out.result)) { + torture_comment(tctx, "SetSecret failed - %s\n", nt_errstr(r5.out.result)); + ret = false; + } + + data_blob_free(&enc_key); + + ZERO_STRUCT(new_mtime); + ZERO_STRUCT(old_mtime); + + /* fetch the secret back again */ + r6.in.sec_handle = &sec_handle; + r6.in.new_val = &bufp1; + r6.in.new_mtime = &new_mtime; + r6.in.old_val = &bufp2; + r6.in.old_mtime = &old_mtime; + + bufp1.buf = NULL; + bufp2.buf = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(b, tctx, &r6), + "QuerySecret failed"); + if (!NT_STATUS_IS_OK(r6.out.result)) { + torture_comment(tctx, "QuerySecret failed - %s\n", nt_errstr(r6.out.result)); + ret = false; + secret4 = NULL; + } else { + + if (r6.out.new_val->buf == NULL || r6.out.old_val->buf == NULL + || r6.out.new_mtime == NULL || r6.out.old_mtime == NULL) { + torture_comment(tctx, "Both secret buffers and both times not returned\n"); + ret = false; + secret4 = NULL; + } else { + blob1.data = r6.out.new_val->buf->data; + blob1.length = r6.out.new_val->buf->size; + + secret4 = sess_decrypt_string(tctx, + &blob1, &session_key); + + if (strcmp(secret3, secret4) != 0) { + torture_comment(tctx, "Returned NEW secret %s doesn't match %s\n", secret4, secret3); + ret = false; + } + + blob1.data = r6.out.old_val->buf->data; + blob1.length = r6.out.old_val->buf->length; + + secret2 = sess_decrypt_string(tctx, + &blob1, &session_key); + + if (strcmp(secret1, secret2) != 0) { + torture_comment(tctx, "Returned OLD secret %s doesn't match %s\n", secret2, secret1); + ret = false; + } + + if (*r6.out.new_mtime == *r6.out.old_mtime) { + torture_comment(tctx, "Returned secret (r6-%d) %s must not have same mtime for both secrets: %s != %s\n", + i, + secname[i], + nt_time_string(tctx, *r6.out.old_mtime), + nt_time_string(tctx, *r6.out.new_mtime)); + ret = false; + } + } + } + + enc_key = sess_encrypt_string(secret5, &session_key); + + r7.in.sec_handle = &sec_handle; + r7.in.old_val = &buf1; + r7.in.old_val->data = enc_key.data; + r7.in.old_val->length = enc_key.length; + r7.in.old_val->size = enc_key.length; + r7.in.new_val = NULL; + + torture_comment(tctx, "Testing SetSecret of old Secret only\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r7), + "SetSecret failed"); + if (!NT_STATUS_IS_OK(r7.out.result)) { + torture_comment(tctx, "SetSecret failed - %s\n", nt_errstr(r7.out.result)); + ret = false; + } + + data_blob_free(&enc_key); + + /* fetch the secret back again */ + r8.in.sec_handle = &sec_handle; + r8.in.new_val = &bufp1; + r8.in.new_mtime = &new_mtime; + r8.in.old_val = &bufp2; + r8.in.old_mtime = &old_mtime; + + bufp1.buf = NULL; + bufp2.buf = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(b, tctx, &r8), + "QuerySecret failed"); + if (!NT_STATUS_IS_OK(r8.out.result)) { + torture_comment(tctx, "QuerySecret failed - %s\n", nt_errstr(r8.out.result)); + ret = false; + } else { + if (!r8.out.new_val || !r8.out.old_val) { + torture_comment(tctx, "in/out pointers not returned, despite being set on in for QuerySecret\n"); + ret = false; + } else if (r8.out.new_val->buf != NULL) { + torture_comment(tctx, "NEW secret buffer must not be returned after OLD set\n"); + ret = false; + } else if (r8.out.old_val->buf == NULL) { + torture_comment(tctx, "OLD secret buffer was not returned after OLD set\n"); + ret = false; + } else if (r8.out.new_mtime == NULL || r8.out.old_mtime == NULL) { + torture_comment(tctx, "Both times not returned after OLD set\n"); + ret = false; + } else { + blob1.data = r8.out.old_val->buf->data; + blob1.length = r8.out.old_val->buf->size; + + secret6 = sess_decrypt_string(tctx, + &blob1, &session_key); + + if (strcmp(secret5, secret6) != 0) { + torture_comment(tctx, "Returned OLD secret %s doesn't match %s\n", secret5, secret6); + ret = false; + } + + if (*r8.out.new_mtime != *r8.out.old_mtime) { + torture_comment(tctx, "Returned secret (r8) %s did not had same mtime for both secrets: %s != %s\n", + secname[i], + nt_time_string(tctx, *r8.out.old_mtime), + nt_time_string(tctx, *r8.out.new_mtime)); + ret = false; + } + } + } + + if (!test_Delete(b, tctx, &sec_handle)) { + ret = false; + } + + if (!test_DeleteObject(b, tctx, &sec_handle)) { + return false; + } + + d_o.in.handle = &sec_handle2; + d_o.out.handle = &sec_handle2; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_DeleteObject_r(b, tctx, &d_o), + "DeleteObject failed"); + torture_assert_ntstatus_equal(tctx, d_o.out.result, NT_STATUS_INVALID_HANDLE, + "OpenSecret expected INVALID_HANDLE"); + + torture_comment(tctx, "Testing OpenSecret of just-deleted secret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenSecret_r(b, tctx, &r2), + "OpenSecret failed"); + torture_assert_ntstatus_equal(tctx, r2.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND, + "OpenSecret expected OBJECT_NAME_NOT_FOUND"); + } + return ret; +} + + +static bool test_EnumAccountRights(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *acct_handle, + struct dom_sid *sid) +{ + struct lsa_EnumAccountRights r; + struct lsa_RightSet rights; + + torture_comment(tctx, "\nTesting EnumAccountRights\n"); + + r.in.handle = acct_handle; + r.in.sid = sid; + r.out.rights = &rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountRights_r(b, tctx, &r), + "EnumAccountRights failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "EnumAccountRights of %s failed - %s\n", + dom_sid_string(tctx, sid), nt_errstr(r.out.result)); + } + torture_assert_ntstatus_ok(tctx, r.out.result, + "EnumAccountRights failed"); + + return true; +} + + +static bool test_QuerySecurity(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct policy_handle *acct_handle) +{ + struct lsa_QuerySecurity r; + struct sec_desc_buf *sdbuf = NULL; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "\nskipping QuerySecurity test against Samba4\n"); + return true; + } + + torture_comment(tctx, "\nTesting QuerySecurity\n"); + + r.in.handle = acct_handle; + r.in.sec_info = SECINFO_OWNER | + SECINFO_GROUP | + SECINFO_DACL; + r.out.sdbuf = &sdbuf; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecurity_r(b, tctx, &r), + "QuerySecurity failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "QuerySecurity failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + return true; +} + +static bool test_OpenAccount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct dom_sid *sid) +{ + struct lsa_OpenAccount r; + struct policy_handle acct_handle; + + torture_comment(tctx, "\nTesting OpenAccount\n"); + + r.in.handle = handle; + r.in.sid = sid; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.acct_handle = &acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenAccount_r(b, tctx, &r), + "OpenAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "OpenAccount failed"); + + if (!test_EnumPrivsAccount(b, tctx, handle, &acct_handle)) { + return false; + } + + if (!test_GetSystemAccessAccount(b, tctx, handle, &acct_handle)) { + return false; + } + + if (!test_QuerySecurity(b, tctx, handle, &acct_handle)) { + return false; + } + + return true; +} + +static bool test_EnumAccounts(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_EnumAccounts r; + struct lsa_SidArray sids1, sids2; + uint32_t resume_handle = 0; + int i; + bool ret = true; + + torture_comment(tctx, "\nTesting EnumAccounts\n"); + + r.in.handle = handle; + r.in.resume_handle = &resume_handle; + r.in.num_entries = 100; + r.out.resume_handle = &resume_handle; + r.out.sids = &sids1; + + resume_handle = 0; + while (true) { + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccounts_r(b, tctx, &r), + "EnumAccounts failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES)) { + break; + } + torture_assert_ntstatus_ok(tctx, r.out.result, + "EnumAccounts failed"); + + if (!test_LookupSids(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &sids1)) { + return false; + } + + if (!test_LookupSids2(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &sids1)) { + return false; + } + + /* Can't test lookupSids3 here, as clearly we must not + * be on schannel, or we would not be able to do the + * rest */ + + torture_comment(tctx, "Testing all accounts\n"); + for (i=0;i<sids1.num_sids;i++) { + ret &= test_OpenAccount(b, tctx, handle, sids1.sids[i].sid); + ret &= test_EnumAccountRights(b, tctx, handle, sids1.sids[i].sid); + } + torture_comment(tctx, "\n"); + } + + if (sids1.num_sids < 3) { + return ret; + } + + torture_comment(tctx, "Trying EnumAccounts partial listing (asking for 1 at 2)\n"); + resume_handle = 2; + r.in.num_entries = 1; + r.out.sids = &sids2; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccounts_r(b, tctx, &r), + "EnumAccounts failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "EnumAccounts failed"); + + if (sids2.num_sids != 1) { + torture_comment(tctx, "Returned wrong number of entries (%d)\n", sids2.num_sids); + return false; + } + + return true; +} + +static bool test_LookupPrivDisplayName(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_String *priv_name) +{ + struct lsa_LookupPrivDisplayName r; + /* produce a reasonable range of language output without screwing up + terminals */ + uint16_t language_id = (random() % 4) + 0x409; + uint16_t returned_language_id = 0; + struct lsa_StringLarge *disp_name = NULL; + + torture_comment(tctx, "\nTesting LookupPrivDisplayName(%s)\n", priv_name->string); + + r.in.handle = handle; + r.in.name = priv_name; + r.in.language_id = language_id; + r.in.language_id_sys = 0; + r.out.returned_language_id = &returned_language_id; + r.out.disp_name = &disp_name; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupPrivDisplayName_r(b, tctx, &r), + "LookupPrivDisplayName failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "LookupPrivDisplayName failed - %s\n", nt_errstr(r.out.result)); + return false; + } + torture_comment(tctx, "%s -> \"%s\" (language 0x%x/0x%x)\n", + priv_name->string, disp_name->string, + r.in.language_id, *r.out.returned_language_id); + + return true; +} + +static bool test_EnumAccountsWithUserRight(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_String *priv_name) +{ + struct lsa_EnumAccountsWithUserRight r; + struct lsa_SidArray sids; + + ZERO_STRUCT(sids); + + torture_comment(tctx, "\nTesting EnumAccountsWithUserRight(%s)\n", priv_name->string); + + r.in.handle = handle; + r.in.name = priv_name; + r.out.sids = &sids; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountsWithUserRight_r(b, tctx, &r), + "EnumAccountsWithUserRight failed"); + + /* NT_STATUS_NO_MORE_ENTRIES means no one has this privilege */ + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES)) { + return true; + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "EnumAccountsWithUserRight failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + return true; +} + + +static bool test_EnumPrivs(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_EnumPrivs r; + struct lsa_PrivArray privs1; + uint32_t resume_handle = 0; + int i; + bool ret = true; + + torture_comment(tctx, "\nTesting EnumPrivs\n"); + + r.in.handle = handle; + r.in.resume_handle = &resume_handle; + r.in.max_count = 100; + r.out.resume_handle = &resume_handle; + r.out.privs = &privs1; + + resume_handle = 0; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumPrivs_r(b, tctx, &r), + "EnumPrivs failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "EnumPrivs failed"); + + for (i = 0; i< privs1.count; i++) { + test_LookupPrivDisplayName(b, tctx, handle, (struct lsa_String *)&privs1.privs[i].name); + test_LookupPrivValue(b, tctx, handle, (struct lsa_String *)&privs1.privs[i].name); + if (!test_EnumAccountsWithUserRight(b, tctx, handle, (struct lsa_String *)&privs1.privs[i].name)) { + ret = false; + } + } + + return ret; +} + +static bool test_QueryForestTrustInformation(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *trusted_domain_name) +{ + bool ret = true; + struct lsa_lsaRQueryForestTrustInformation r; + struct lsa_String string; + struct lsa_ForestTrustInformation info, *info_ptr; + + torture_comment(tctx, "\nTesting lsaRQueryForestTrustInformation\n"); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping QueryForestTrustInformation against Samba4\n"); + return true; + } + + ZERO_STRUCT(string); + + if (trusted_domain_name) { + init_lsa_String(&string, trusted_domain_name); + } + + info_ptr = &info; + + r.in.handle = handle; + r.in.trusted_domain_name = &string; + r.in.highest_record_type = LSA_FOREST_TRUST_TOP_LEVEL_NAME; + r.out.forest_trust_info = &info_ptr; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_lsaRQueryForestTrustInformation_r(b, tctx, &r), + "lsaRQueryForestTrustInformation failed"); + + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "lsaRQueryForestTrustInformation of %s failed - %s\n", trusted_domain_name, nt_errstr(r.out.result)); + ret = false; + } + + return ret; +} + +static bool test_query_each_TrustDomEx(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_DomainListEx *domains) +{ + int i; + bool ret = true; + + for (i=0; i< domains->count; i++) { + + if (domains->domains[i].trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + ret &= test_QueryForestTrustInformation(b, tctx, handle, + domains->domains[i].domain_name.string); + } + } + + return ret; +} + +static bool test_query_each_TrustDom(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + struct lsa_DomainList *domains) +{ + int i,j; + bool ret = true; + + torture_comment(tctx, "\nTesting OpenTrustedDomain, OpenTrustedDomainByName and QueryInfoTrustedDomain\n"); + for (i=0; i< domains->count; i++) { + struct lsa_OpenTrustedDomain trust; + struct lsa_OpenTrustedDomainByName trust_by_name; + struct policy_handle trustdom_handle; + struct policy_handle handle2; + struct lsa_Close c; + struct lsa_CloseTrustedDomainEx c_trust; + int levels [] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + int ok[] = {1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1}; + + if (domains->domains[i].sid) { + trust.in.handle = handle; + trust.in.sid = domains->domains[i].sid; + trust.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + trust.out.trustdom_handle = &trustdom_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenTrustedDomain_r(b, tctx, &trust), + "OpenTrustedDomain failed"); + + if (NT_STATUS_EQUAL(trust.out.result, NT_STATUS_NO_SUCH_DOMAIN)) { + torture_comment(tctx, "DOMAIN(%s, %s) not a direct trust?\n", + domains->domains[i].name.string, + dom_sid_string(tctx, domains->domains[i].sid)); + continue; + } + if (!NT_STATUS_IS_OK(trust.out.result)) { + torture_comment(tctx, "OpenTrustedDomain failed - %s\n", nt_errstr(trust.out.result)); + return false; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &handle2; + + c_trust.in.handle = &trustdom_handle; + c_trust.out.handle = &handle2; + + for (j=0; j < ARRAY_SIZE(levels); j++) { + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info = NULL; + q.in.trustdom_handle = &trustdom_handle; + q.in.level = levels[j]; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfo_r(b, tctx, &q), + "QueryTrustedDomainInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result) && ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfo level %d failed - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } else if (NT_STATUS_IS_OK(q.out.result) && !ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfo level %d unexpectedly succeeded - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } + } + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CloseTrustedDomainEx_r(b, tctx, &c_trust), + "CloseTrustedDomainEx failed"); + if (!NT_STATUS_EQUAL(c_trust.out.result, NT_STATUS_NOT_IMPLEMENTED)) { + torture_comment(tctx, "Expected CloseTrustedDomainEx to return NT_STATUS_NOT_IMPLEMENTED, instead - %s\n", nt_errstr(c_trust.out.result)); + return false; + } + + c.in.handle = &trustdom_handle; + c.out.handle = &handle2; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_Close_r(b, tctx, &c), + "Close failed"); + if (!NT_STATUS_IS_OK(c.out.result)) { + torture_comment(tctx, "Close of trusted domain failed - %s\n", nt_errstr(c.out.result)); + return false; + } + + for (j=0; j < ARRAY_SIZE(levels); j++) { + struct lsa_QueryTrustedDomainInfoBySid q; + union lsa_TrustedDomainInfo *info = NULL; + + if (!domains->domains[i].sid) { + continue; + } + + q.in.handle = handle; + q.in.dom_sid = domains->domains[i].sid; + q.in.level = levels[j]; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfoBySid_r(b, tctx, &q), + "lsa_QueryTrustedDomainInfoBySid failed"); + if (!NT_STATUS_IS_OK(q.out.result) && ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfoBySid level %d failed - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } else if (NT_STATUS_IS_OK(q.out.result) && !ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfoBySid level %d unexpectedly succeeded - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } + } + } + + trust_by_name.in.handle = handle; + trust_by_name.in.name.string = domains->domains[i].name.string; + trust_by_name.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + trust_by_name.out.trustdom_handle = &trustdom_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenTrustedDomainByName_r(b, tctx, &trust_by_name), + "OpenTrustedDomainByName failed"); + + if (NT_STATUS_EQUAL(trust_by_name.out.result, NT_STATUS_NO_SUCH_DOMAIN)) { + torture_comment(tctx, "DOMAIN(%s, %s) not a direct trust?\n", + domains->domains[i].name.string, + dom_sid_string(tctx, domains->domains[i].sid)); + continue; + } + if (!NT_STATUS_IS_OK(trust_by_name.out.result)) { + torture_comment(tctx, "OpenTrustedDomainByName failed - %s\n", nt_errstr(trust_by_name.out.result)); + return false; + } + + for (j=0; j < ARRAY_SIZE(levels); j++) { + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info = NULL; + q.in.trustdom_handle = &trustdom_handle; + q.in.level = levels[j]; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfo_r(b, tctx, &q), + "QueryTrustedDomainInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result) && ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfo level %d failed - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } else if (NT_STATUS_IS_OK(q.out.result) && !ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfo level %d unexpectedly succeeded - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } + } + + c.in.handle = &trustdom_handle; + c.out.handle = &handle2; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_Close_r(b, tctx, &c), + "Close failed"); + if (!NT_STATUS_IS_OK(c.out.result)) { + torture_comment(tctx, "Close of trusted domain failed - %s\n", nt_errstr(c.out.result)); + return false; + } + + for (j=0; j < ARRAY_SIZE(levels); j++) { + struct lsa_QueryTrustedDomainInfoByName q; + union lsa_TrustedDomainInfo *info = NULL; + struct lsa_String name; + + name.string = domains->domains[i].name.string; + + q.in.handle = handle; + q.in.trusted_domain = &name; + q.in.level = levels[j]; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfoByName_r(b, tctx, &q), + "QueryTrustedDomainInfoByName failed"); + if (!NT_STATUS_IS_OK(q.out.result) && ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfoByName level %d failed - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } else if (NT_STATUS_IS_OK(q.out.result) && !ok[j]) { + torture_comment(tctx, "QueryTrustedDomainInfoByName level %d unexpectedly succeeded - %s\n", + levels[j], nt_errstr(q.out.result)); + ret = false; + } + } + } + return ret; +} + +static bool test_EnumTrustDom(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_EnumTrustDom r; + uint32_t in_resume_handle = 0; + uint32_t out_resume_handle; + struct lsa_DomainList domains; + bool ret = true; + + torture_comment(tctx, "\nTesting EnumTrustDom\n"); + + r.in.handle = handle; + r.in.resume_handle = &in_resume_handle; + r.in.max_size = 0; + r.out.domains = &domains; + r.out.resume_handle = &out_resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustDom_r(b, tctx, &r), + "lsa_EnumTrustDom failed"); + + /* 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 */ + + if (NT_STATUS_IS_OK(r.out.result) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES) || + NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) + { + if (out_resume_handle <= in_resume_handle) { + torture_comment(tctx, "EnumTrustDom failed - should have returned output resume_handle (0x%08x) larger than input resume handle (0x%08x)\n", + out_resume_handle, in_resume_handle); + return false; + } + } + + if (NT_STATUS_IS_OK(r.out.result)) { + if (domains.count == 0) { + torture_comment(tctx, "EnumTrustDom failed - should have returned 'NT_STATUS_NO_MORE_ENTRIES' for 0 trusted domains\n"); + return false; + } + } else if (!(NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) || NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES))) { + torture_comment(tctx, "EnumTrustDom of zero size failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + /* Start from the bottom again */ + in_resume_handle = 0; + + do { + r.in.handle = handle; + r.in.resume_handle = &in_resume_handle; + r.in.max_size = LSA_ENUM_TRUST_DOMAIN_MULTIPLIER * 3; + r.out.domains = &domains; + r.out.resume_handle = &out_resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustDom_r(b, tctx, &r), + "EnumTrustDom failed"); + + /* 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 */ + + if (NT_STATUS_IS_OK(r.out.result) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES) || + NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) + { + if (out_resume_handle <= in_resume_handle) { + torture_comment(tctx, "EnumTrustDom failed - should have returned output resume_handle (0x%08x) larger than input resume handle (0x%08x)\n", + out_resume_handle, in_resume_handle); + return false; + } + } + + /* NO_MORE_ENTRIES is allowed */ + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES)) { + if (domains.count == 0) { + return true; + } + torture_comment(tctx, "EnumTrustDom failed - should have returned 0 trusted domains with 'NT_STATUS_NO_MORE_ENTRIES'\n"); + return false; + } else if (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) { + /* Windows 2003 gets this off by one on the first run */ + if (r.out.domains->count < 3 || r.out.domains->count > 4) { + torture_comment(tctx, "EnumTrustDom didn't fill the buffer we " + "asked it to (got %d, expected %d / %d == %d entries)\n", + r.out.domains->count, LSA_ENUM_TRUST_DOMAIN_MULTIPLIER * 3, + LSA_ENUM_TRUST_DOMAIN_MULTIPLIER, r.in.max_size); + ret = false; + } + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "EnumTrustDom failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + if (domains.count == 0) { + torture_comment(tctx, "EnumTrustDom failed - should have returned 'NT_STATUS_NO_MORE_ENTRIES' for 0 trusted domains\n"); + return false; + } + + ret &= test_query_each_TrustDom(b, tctx, handle, &domains); + + in_resume_handle = out_resume_handle; + + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + + return ret; +} + +static bool test_EnumTrustDomEx(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_EnumTrustedDomainsEx r_ex; + uint32_t in_resume_handle = 0; + uint32_t out_resume_handle; + struct lsa_DomainListEx domains_ex; + bool ret = true; + + torture_comment(tctx, "\nTesting EnumTrustedDomainsEx\n"); + + r_ex.in.handle = handle; + r_ex.in.resume_handle = &in_resume_handle; + r_ex.in.max_size = 0; + r_ex.out.domains = &domains_ex; + r_ex.out.resume_handle = &out_resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustedDomainsEx_r(b, tctx, &r_ex), + "EnumTrustedDomainsEx failed"); + + /* 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 */ + + if (NT_STATUS_IS_OK(r_ex.out.result) || + NT_STATUS_EQUAL(r_ex.out.result, NT_STATUS_NO_MORE_ENTRIES) || + NT_STATUS_EQUAL(r_ex.out.result, STATUS_MORE_ENTRIES)) + { + if (out_resume_handle <= in_resume_handle) { + torture_comment(tctx, "EnumTrustDomEx failed - should have returned output resume_handle (0x%08x) larger than input resume handle (0x%08x)\n", + out_resume_handle, in_resume_handle); + return false; + } + } + + if (NT_STATUS_IS_OK(r_ex.out.result)) { + if (domains_ex.count == 0) { + torture_comment(tctx, "EnumTrustDom failed - should have returned 'NT_STATUS_NO_MORE_ENTRIES' for 0 trusted domains\n"); + return false; + } + } else if (!(NT_STATUS_EQUAL(r_ex.out.result, STATUS_MORE_ENTRIES) || + NT_STATUS_EQUAL(r_ex.out.result, NT_STATUS_NO_MORE_ENTRIES))) { + torture_comment(tctx, "EnumTrustDom of zero size failed - %s\n", + nt_errstr(r_ex.out.result)); + return false; + } + + in_resume_handle = 0; + do { + r_ex.in.handle = handle; + r_ex.in.resume_handle = &in_resume_handle; + r_ex.in.max_size = LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER * 3; + r_ex.out.domains = &domains_ex; + r_ex.out.resume_handle = &out_resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustedDomainsEx_r(b, tctx, &r_ex), + "EnumTrustedDomainsEx failed"); + + in_resume_handle = out_resume_handle; + + /* NO_MORE_ENTRIES is allowed */ + if (NT_STATUS_EQUAL(r_ex.out.result, NT_STATUS_NO_MORE_ENTRIES)) { + if (domains_ex.count == 0) { + return true; + } + torture_comment(tctx, "EnumTrustDomainsEx failed - should have returned 0 trusted domains with 'NT_STATUS_NO_MORE_ENTRIES'\n"); + return false; + } else if (NT_STATUS_EQUAL(r_ex.out.result, STATUS_MORE_ENTRIES)) { + /* Windows 2003 gets this off by one on the first run */ + if (r_ex.out.domains->count < 3 || r_ex.out.domains->count > 4) { + torture_comment(tctx, "EnumTrustDom didn't fill the buffer we " + "asked it to (got %d, expected %d / %d == %d entries)\n", + r_ex.out.domains->count, + r_ex.in.max_size, + LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER, + r_ex.in.max_size / LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER); + } + } else if (!NT_STATUS_IS_OK(r_ex.out.result)) { + torture_comment(tctx, "EnumTrustedDomainEx failed - %s\n", nt_errstr(r_ex.out.result)); + return false; + } + + if (domains_ex.count == 0) { + torture_comment(tctx, "EnumTrustDomainEx failed - should have returned 'NT_STATUS_NO_MORE_ENTRIES' for 0 trusted domains\n"); + return false; + } + + ret &= test_query_each_TrustDomEx(b, tctx, handle, &domains_ex); + + } while (NT_STATUS_EQUAL(r_ex.out.result, STATUS_MORE_ENTRIES)); + + return ret; +} + + +static bool test_CreateTrustedDomain(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_trusts) +{ + bool ret = true; + struct lsa_CreateTrustedDomain r; + struct lsa_DomainInfo trustinfo; + struct dom_sid **domsid; + struct policy_handle *trustdom_handle; + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info = NULL; + int i; + + torture_comment(tctx, "\nTesting CreateTrustedDomain for %d domains\n", num_trusts); + + if (!test_EnumTrustDom(b, tctx, handle)) { + ret = false; + } + + if (!test_EnumTrustDomEx(b, tctx, handle)) { + ret = false; + } + + domsid = talloc_array(tctx, struct dom_sid *, num_trusts); + trustdom_handle = talloc_array(tctx, struct policy_handle, num_trusts); + + for (i=0; i< num_trusts; i++) { + char *trust_name = talloc_asprintf(tctx, "TORTURE1%02d", i); + char *trust_sid = talloc_asprintf(tctx, "S-1-5-21-97398-379795-1%02d", i); + + domsid[i] = dom_sid_parse_talloc(tctx, trust_sid); + + trustinfo.sid = domsid[i]; + init_lsa_String((struct lsa_String *)&trustinfo.name, trust_name); + + r.in.policy_handle = handle; + r.in.info = &trustinfo; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.trustdom_handle = &trustdom_handle[i]; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateTrustedDomain_r(b, tctx, &r), + "CreateTrustedDomain failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_OBJECT_NAME_COLLISION)) { + test_DeleteTrustedDomain(b, tctx, handle, trustinfo.name); + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateTrustedDomain_r(b, tctx, &r), + "CreateTrustedDomain failed"); + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "CreateTrustedDomain failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } else { + + q.in.trustdom_handle = &trustdom_handle[i]; + q.in.level = LSA_TRUSTED_DOMAIN_INFO_INFO_EX; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfo_r(b, tctx, &q), + "QueryTrustedDomainInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_comment(tctx, "QueryTrustedDomainInfo level %d failed - %s\n", q.in.level, nt_errstr(q.out.result)); + ret = false; + } else if (!q.out.info) { + ret = false; + } else { + if (strcmp(info->info_ex.domain_name.string, trustinfo.name.string) != 0) { + torture_comment(tctx, "QueryTrustedDomainInfo returned inconsistent long name: %s != %s\n", + info->info_ex.domain_name.string, trustinfo.name.string); + ret = false; + } + if (strcmp(info->info_ex.netbios_name.string, trustinfo.name.string) != 0) { + torture_comment(tctx, "QueryTrustedDomainInfo returned inconsistent short name: %s != %s\n", + info->info_ex.netbios_name.string, trustinfo.name.string); + ret = false; + } + if (info->info_ex.trust_type != LSA_TRUST_TYPE_DOWNLEVEL) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust type %d != %d\n", + trust_name, info->info_ex.trust_type, LSA_TRUST_TYPE_DOWNLEVEL); + ret = false; + } + if (info->info_ex.trust_attributes != 0) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust attributes %d != %d\n", + trust_name, info->info_ex.trust_attributes, 0); + ret = false; + } + if (info->info_ex.trust_direction != LSA_TRUST_DIRECTION_OUTBOUND) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust direction %d != %d\n", + trust_name, info->info_ex.trust_direction, LSA_TRUST_DIRECTION_OUTBOUND); + ret = false; + } + } + } + } + + /* now that we have some domains to look over, we can test the enum calls */ + if (!test_EnumTrustDom(b, tctx, handle)) { + ret = false; + } + + if (!test_EnumTrustDomEx(b, tctx, handle)) { + ret = false; + } + + for (i=0; i<num_trusts; i++) { + if (!test_DeleteTrustedDomainBySid(b, tctx, handle, domsid[i])) { + ret = false; + } + } + + return ret; +} + +static bool gen_authinfo_internal(TALLOC_CTX *mem_ctx, + const char *incoming_old, const char *incoming_new, + const char *outgoing_old, const char *outgoing_new, + DATA_BLOB session_key, + struct lsa_TrustDomainInfoAuthInfoInternal **_authinfo_internal) +{ + struct lsa_TrustDomainInfoAuthInfoInternal *authinfo_internal; + struct trustDomainPasswords auth_struct; + struct AuthenticationInformation in_info; + struct AuthenticationInformation io_info; + struct AuthenticationInformation on_info; + struct AuthenticationInformation oo_info; + size_t converted_size; + DATA_BLOB auth_blob; + enum ndr_err_code ndr_err; + bool ok; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + + authinfo_internal = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoAuthInfoInternal); + if (authinfo_internal == NULL) { + return false; + } + + in_info.AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, + incoming_new, + strlen(incoming_new), + &in_info.AuthInfo.clear.password, + &converted_size); + if (!ok) { + return false; + } + in_info.AuthInfo.clear.size = converted_size; + + io_info.AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, + incoming_old, + strlen(incoming_old), + &io_info.AuthInfo.clear.password, + &converted_size); + if (!ok) { + return false; + } + io_info.AuthInfo.clear.size = converted_size; + + on_info.AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, + outgoing_new, + strlen(outgoing_new), + &on_info.AuthInfo.clear.password, + &converted_size); + if (!ok) { + return false; + } + on_info.AuthInfo.clear.size = converted_size; + + oo_info.AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, + outgoing_old, + strlen(outgoing_old), + &oo_info.AuthInfo.clear.password, + &converted_size); + if (!ok) { + return false; + } + oo_info.AuthInfo.clear.size = converted_size; + + generate_random_buffer(auth_struct.confounder, sizeof(auth_struct.confounder)); + auth_struct.outgoing.count = 1; + auth_struct.outgoing.current.count = 1; + auth_struct.outgoing.current.array = &on_info; + auth_struct.outgoing.previous.count = 1; + auth_struct.outgoing.previous.array = &oo_info; + + auth_struct.incoming.count = 1; + auth_struct.incoming.current.count = 1; + auth_struct.incoming.current.array = &in_info; + auth_struct.incoming.previous.count = 1; + auth_struct.incoming.previous.array = &io_info; + + ndr_err = ndr_push_struct_blob(&auth_blob, mem_ctx, &auth_struct, + (ndr_push_flags_fn_t)ndr_push_trustDomainPasswords); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, + auth_blob.data, + auth_blob.length); + gnutls_cipher_deinit(cipher_hnd); + + authinfo_internal->auth_blob.size = auth_blob.length; + authinfo_internal->auth_blob.data = auth_blob.data; + + *_authinfo_internal = authinfo_internal; + + return true; +} + +static bool gen_authinfo(TALLOC_CTX *mem_ctx, + const char *incoming_old, const char *incoming_new, + const char *outgoing_old, const char *outgoing_new, + struct lsa_TrustDomainInfoAuthInfo **_authinfo) +{ + struct lsa_TrustDomainInfoAuthInfo *authinfo; + struct lsa_TrustDomainInfoBuffer *in_buffer; + struct lsa_TrustDomainInfoBuffer *io_buffer; + struct lsa_TrustDomainInfoBuffer *on_buffer; + struct lsa_TrustDomainInfoBuffer *oo_buffer; + size_t converted_size; + bool ok; + + authinfo = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoAuthInfo); + if (authinfo == NULL) { + return false; + } + + in_buffer = talloc_zero(authinfo, struct lsa_TrustDomainInfoBuffer); + if (in_buffer == NULL) { + return false; + } + in_buffer->AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(in_buffer, CH_UNIX, CH_UTF16, + incoming_new, + strlen(incoming_new), + &in_buffer->data.data, + &converted_size); + if (!ok) { + return false; + } + in_buffer->data.size = converted_size; + + io_buffer = talloc_zero(authinfo, struct lsa_TrustDomainInfoBuffer); + if (io_buffer == NULL) { + return false; + } + io_buffer->AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(io_buffer, CH_UNIX, CH_UTF16, + incoming_old, + strlen(incoming_old), + &io_buffer->data.data, + &converted_size); + if (!ok) { + return false; + } + io_buffer->data.size = converted_size; + + on_buffer = talloc_zero(authinfo, struct lsa_TrustDomainInfoBuffer); + if (on_buffer == NULL) { + return false; + } + on_buffer->AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(on_buffer, CH_UNIX, CH_UTF16, + outgoing_new, + strlen(outgoing_new), + &on_buffer->data.data, + &converted_size); + if (!ok) { + return false; + } + on_buffer->data.size = converted_size; + + oo_buffer = talloc_zero(authinfo, struct lsa_TrustDomainInfoBuffer); + if (oo_buffer == NULL) { + return false; + } + oo_buffer->AuthType = TRUST_AUTH_TYPE_CLEAR; + ok = convert_string_talloc(oo_buffer, CH_UNIX, CH_UTF16, + outgoing_old, + strlen(outgoing_old), + &oo_buffer->data.data, + &converted_size); + if (!ok) { + return false; + } + oo_buffer->data.size = converted_size; + + authinfo->incoming_count = 1; + authinfo->incoming_current_auth_info = in_buffer; + authinfo->incoming_previous_auth_info = io_buffer; + authinfo->outgoing_count = 1; + authinfo->outgoing_current_auth_info = on_buffer; + authinfo->outgoing_previous_auth_info = oo_buffer; + + *_authinfo = authinfo; + + return true; +} + +static bool check_pw_with_ServerAuthenticate3(struct dcerpc_pipe *p, + struct torture_context *tctx, + uint32_t negotiate_flags, + const char *server_name, + struct cli_credentials *machine_credentials, + struct netlogon_creds_CredentialState **creds_out) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + const struct samr_Password *new_password = NULL; + const struct samr_Password *old_password = NULL; + uint32_t rid; + struct dcerpc_binding_handle *b = p->binding_handle; + + new_password = cli_credentials_get_nt_hash(machine_credentials, tctx); + old_password = cli_credentials_get_old_nt_hash(machine_credentials, tctx); + + r.in.server_name = server_name; + r.in.computer_name = cli_credentials_get_workstation(machine_credentials); + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + a.in.server_name = server_name; + a.in.account_name = cli_credentials_get_username(machine_credentials); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = cli_credentials_get_workstation(machine_credentials); + a.in.negotiate_flags = &negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &negotiate_flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + new_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + if (!NT_STATUS_IS_OK(a.out.result)) { + if (!NT_STATUS_EQUAL(a.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_assert_ntstatus_ok(tctx, a.out.result, + "ServerAuthenticate3 failed"); + } + return false; + } + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + if (old_password != NULL) { + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + old_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + if (!NT_STATUS_IS_OK(a.out.result)) { + if (!NT_STATUS_EQUAL(a.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_assert_ntstatus_ok(tctx, a.out.result, + "ServerAuthenticate3 (old) failed"); + } + return false; + } + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential (old) chaining failed"); + } + + /* Prove that requesting a challenge again won't break it */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + *creds_out = creds; + return true; +} + +#ifdef SAMBA4_USES_HEIMDAL + +/* + * This function is set in torture_krb5_init_context as krb5 + * send_and_recv function. This allows us to override what server the + * test is aimed at, and to inspect the packets just before they are + * sent to the network, and before they are processed on the recv + * side. + * + * The torture_krb5_pre_send_test() and torture_krb5_post_recv_test() + * functions are implement the actual tests. + * + * When this asserts, the caller will get a spurious 'cannot contact + * any KDC' message. + * + */ +struct check_pw_with_krb5_ctx { + struct addrinfo *server; + const char *server_nb_domain; + const char *server_dns_domain; + struct { + unsigned io; + unsigned fail; + unsigned errors; + unsigned error_io; + unsigned ok; + } counts; + krb5_error error; + struct smb_krb5_context *smb_krb5_context; + krb5_get_init_creds_opt *krb_options; + krb5_creds my_creds; + krb5_get_creds_opt opt_canon; + krb5_get_creds_opt opt_nocanon; + krb5_principal upn_realm; + krb5_principal upn_dns; + krb5_principal upn_netbios; + krb5_ccache krbtgt_ccache; + krb5_principal krbtgt_trust_realm; + krb5_creds *krbtgt_trust_realm_creds; + krb5_principal krbtgt_trust_dns; + krb5_creds *krbtgt_trust_dns_creds; + krb5_principal krbtgt_trust_netbios; + krb5_creds *krbtgt_trust_netbios_creds; + krb5_principal cifs_trust_dns; + krb5_creds *cifs_trust_dns_creds; + krb5_principal cifs_trust_netbios; + krb5_creds *cifs_trust_netbios_creds; + krb5_principal drs_trust_dns; + krb5_creds *drs_trust_dns_creds; + krb5_principal drs_trust_netbios; + krb5_creds *drs_trust_netbios_creds; + krb5_principal four_trust_dns; + krb5_creds *four_trust_dns_creds; + krb5_creds krbtgt_referral_creds; + Ticket krbtgt_referral_ticket; + krb5_keyblock krbtgt_referral_keyblock; + EncTicketPart krbtgt_referral_enc_part; +}; + +static krb5_error_code check_pw_with_krb5_send_to_realm( + struct smb_krb5_context *smb_krb5_context, + void *data, /* struct check_pw_with_krb5_ctx */ + krb5_const_realm realm, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf) +{ + struct check_pw_with_krb5_ctx *ctx = + talloc_get_type_abort(data, struct check_pw_with_krb5_ctx); + krb5_error_code k5ret; + size_t used; + int ret; + + SMB_ASSERT(smb_krb5_context == ctx->smb_krb5_context); + + if (!strequal_m(realm, ctx->server_nb_domain) && + !strequal_m(realm, ctx->server_dns_domain)) + { + return KRB5_KDC_UNREACH; + } + + krb5_free_error_contents(ctx->smb_krb5_context->krb5_context, + &ctx->error); + ctx->counts.io++; + + k5ret = smb_krb5_send_and_recv_func_forced_tcp(ctx->smb_krb5_context, + ctx->server, + timeout, send_buf, recv_buf); + if (k5ret != 0) { + ctx->counts.fail++; + return k5ret; + } + + ret = decode_KRB_ERROR(recv_buf->data, recv_buf->length, + &ctx->error, &used); + if (ret == 0) { + ctx->counts.errors++; + ctx->counts.error_io = ctx->counts.io; + } else { + ctx->counts.ok++; + } + + return k5ret; +} + +static int check_pw_with_krb5_ctx_destructor(struct check_pw_with_krb5_ctx *ctx) +{ + if (ctx->server != NULL) { + freeaddrinfo(ctx->server); + ctx->server = NULL; + } + + if (ctx->krb_options != NULL) { + krb5_get_init_creds_opt_free(ctx->smb_krb5_context->krb5_context, + ctx->krb_options); + ctx->krb_options = NULL; + } + + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds); + + if (ctx->opt_canon != NULL) { + krb5_get_creds_opt_free(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon); + ctx->opt_canon = NULL; + } + + if (ctx->opt_nocanon != NULL) { + krb5_get_creds_opt_free(ctx->smb_krb5_context->krb5_context, + ctx->opt_nocanon); + ctx->opt_nocanon = NULL; + } + + if (ctx->krbtgt_ccache != NULL) { + krb5_cc_close(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache); + ctx->krbtgt_ccache = NULL; + } + + if (ctx->upn_realm != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->upn_realm); + ctx->upn_realm = NULL; + } + + if (ctx->upn_dns != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->upn_dns); + ctx->upn_dns = NULL; + } + + if (ctx->upn_netbios != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->upn_netbios); + ctx->upn_netbios = NULL; + } + + if (ctx->krbtgt_trust_realm != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_realm); + ctx->krbtgt_trust_realm = NULL; + } + + if (ctx->krbtgt_trust_realm_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_realm_creds); + ctx->krbtgt_trust_realm_creds = NULL; + } + + if (ctx->krbtgt_trust_dns != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_dns); + ctx->krbtgt_trust_dns = NULL; + } + + if (ctx->krbtgt_trust_dns_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_dns_creds); + ctx->krbtgt_trust_dns_creds = NULL; + } + + if (ctx->krbtgt_trust_netbios != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_netbios); + ctx->krbtgt_trust_netbios = NULL; + } + + if (ctx->krbtgt_trust_netbios_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_netbios_creds); + ctx->krbtgt_trust_netbios_creds = NULL; + } + + if (ctx->cifs_trust_dns != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->cifs_trust_dns); + ctx->cifs_trust_dns = NULL; + } + + if (ctx->cifs_trust_dns_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->cifs_trust_dns_creds); + ctx->cifs_trust_dns_creds = NULL; + } + + if (ctx->cifs_trust_netbios != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->cifs_trust_netbios); + ctx->cifs_trust_netbios = NULL; + } + + if (ctx->cifs_trust_netbios_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->cifs_trust_netbios_creds); + ctx->cifs_trust_netbios_creds = NULL; + } + + if (ctx->drs_trust_dns != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->drs_trust_dns); + ctx->drs_trust_dns = NULL; + } + + if (ctx->drs_trust_dns_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->drs_trust_dns_creds); + ctx->drs_trust_dns_creds = NULL; + } + + if (ctx->drs_trust_netbios != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->drs_trust_netbios); + ctx->drs_trust_netbios = NULL; + } + + if (ctx->drs_trust_netbios_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->drs_trust_netbios_creds); + ctx->drs_trust_netbios_creds = NULL; + } + + if (ctx->four_trust_dns != NULL) { + krb5_free_principal(ctx->smb_krb5_context->krb5_context, + ctx->four_trust_dns); + ctx->four_trust_dns = NULL; + } + + if (ctx->four_trust_dns_creds != NULL) { + krb5_free_creds(ctx->smb_krb5_context->krb5_context, + ctx->four_trust_dns_creds); + ctx->four_trust_dns_creds = NULL; + } + + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + + free_Ticket(&ctx->krbtgt_referral_ticket); + + krb5_free_keyblock_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_keyblock); + + free_EncTicketPart(&ctx->krbtgt_referral_enc_part); + + krb5_free_error_contents(ctx->smb_krb5_context->krb5_context, + &ctx->error); + + talloc_unlink(ctx, ctx->smb_krb5_context); + ctx->smb_krb5_context = NULL; + return 0; +} + +static bool check_pw_with_krb5(struct torture_context *tctx, + struct cli_credentials *credentials, + const struct lsa_TrustDomainInfoInfoEx *trusted) +{ + const char *trusted_dns_name = trusted->domain_name.string; + const char *trusted_netbios_name = trusted->netbios_name.string; + char *trusted_realm_name = NULL; + krb5_principal principal = NULL; + enum credentials_obtained obtained; + const char *error_string = NULL; + const char *workstation = cli_credentials_get_workstation(credentials); + const char *password = cli_credentials_get_password(credentials); +#ifndef USING_EMBEDDED_HEIMDAL + const struct samr_Password *nthash = NULL; + const struct samr_Password *old_nthash = NULL; +#endif + const char *old_password = cli_credentials_get_old_password(credentials); +#ifndef USING_EMBEDDED_HEIMDAL + int kvno = cli_credentials_get_kvno(credentials); + int expected_kvno = 0; + krb5uint32 t_kvno = 0; +#endif + const char *host = torture_setting_string(tctx, "host", NULL); + krb5_error_code k5ret; + krb5_boolean k5ok; + int type; + bool ok; + struct check_pw_with_krb5_ctx *ctx = NULL; + char *assertion_message = NULL; + const char *realm = NULL; + char *upn_realm_string = NULL; + char *upn_dns_string = NULL; + char *upn_netbios_string = NULL; + char *krbtgt_cc_name = NULL; + char *krbtgt_trust_realm_string = NULL; + char *krbtgt_trust_dns_string = NULL; + char *krbtgt_trust_netbios_string = NULL; + char *cifs_trust_dns_string = NULL; + char *cifs_trust_netbios_string = NULL; + char *drs_trust_dns_string = NULL; + char *drs_trust_netbios_string = NULL; + char *four_trust_dns_string = NULL; + + ctx = talloc_zero(tctx, struct check_pw_with_krb5_ctx); + torture_assert(tctx, ctx != NULL, "Failed to allocate"); + + realm = cli_credentials_get_realm(credentials); + trusted_realm_name = strupper_talloc(tctx, trusted_dns_name); + +#ifndef USING_EMBEDDED_HEIMDAL + nthash = cli_credentials_get_nt_hash(credentials, ctx); + old_nthash = cli_credentials_get_old_nt_hash(credentials, ctx); +#endif + + k5ret = smb_krb5_init_context(ctx, tctx->lp_ctx, &ctx->smb_krb5_context); + torture_assert_int_equal(tctx, k5ret, 0, "smb_krb5_init_context failed"); + + ctx->server_nb_domain = cli_credentials_get_domain(credentials); + ctx->server_dns_domain = cli_credentials_get_realm(credentials); + + ok = interpret_string_addr_internal(&ctx->server, host, 0); + torture_assert(tctx, ok, "Failed to parse target server"); + talloc_set_destructor(ctx, check_pw_with_krb5_ctx_destructor); + + set_sockaddr_port(ctx->server->ai_addr, 88); + + k5ret = smb_krb5_set_send_to_kdc_func(ctx->smb_krb5_context, + check_pw_with_krb5_send_to_realm, + NULL, /* send_to_kdc */ + ctx); + torture_assert_int_equal(tctx, k5ret, 0, "krb5_set_send_to_kdc_func failed"); + + torture_assert_int_equal(tctx, + krb5_get_init_creds_opt_alloc(ctx->smb_krb5_context->krb5_context, + &ctx->krb_options), + 0, "krb5_get_init_creds_opt_alloc failed"); + torture_assert_int_equal(tctx, + krb5_get_init_creds_opt_set_pac_request( + ctx->smb_krb5_context->krb5_context, + ctx->krb_options, true), + 0, "krb5_get_init_creds_opt_set_pac_request failed"); + + upn_realm_string = talloc_asprintf(ctx, "user@%s", + trusted_realm_name); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->upn_realm, + realm, upn_realm_string, NULL), + 0, "smb_krb5_make_principal failed"); + smb_krb5_principal_set_type(ctx->smb_krb5_context->krb5_context, + ctx->upn_realm, KRB5_NT_ENTERPRISE_PRINCIPAL); + + upn_dns_string = talloc_asprintf(ctx, "user@%s", + trusted_dns_name); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->upn_dns, + realm, upn_dns_string, NULL), + 0, "smb_krb5_make_principal failed"); + smb_krb5_principal_set_type(ctx->smb_krb5_context->krb5_context, + ctx->upn_dns, KRB5_NT_ENTERPRISE_PRINCIPAL); + + upn_netbios_string = talloc_asprintf(ctx, "user@%s", + trusted_netbios_name); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->upn_netbios, + realm, upn_netbios_string, NULL), + 0, "smb_krb5_make_principal failed"); + smb_krb5_principal_set_type(ctx->smb_krb5_context->krb5_context, + ctx->upn_netbios, KRB5_NT_ENTERPRISE_PRINCIPAL); + + k5ret = principal_from_credentials(ctx, credentials, ctx->smb_krb5_context, + &principal, &obtained, &error_string); + torture_assert_int_equal(tctx, k5ret, 0, error_string); + + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_init_creds_password(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds, ctx->upn_realm, + "_none_", NULL, NULL, 0, + NULL, ctx->krb_options); + assertion_message = talloc_asprintf(ctx, + "krb5_get_init_creds_password(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u/%u,ok=%u]", + upn_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.error_io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.error_io, 1, assertion_message); + torture_assert_int_equal(tctx, KRB5_ERROR_CODE(&ctx->error), 68, assertion_message); + torture_assert(tctx, ctx->error.crealm != NULL, assertion_message); + torture_assert_str_equal(tctx, *ctx->error.crealm, trusted_realm_name, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert(tctx, ctx->error.cname != NULL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_type, KRB5_NT_ENTERPRISE_PRINCIPAL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_string.len, 1, assertion_message); + torture_assert_str_equal(tctx, ctx->error.cname->name_string.val[0], upn_realm_string, assertion_message); +#else + torture_assert(tctx, ctx->error.cname == NULL, assertion_message); +#endif + torture_assert_str_equal(tctx, ctx->error.realm, realm, assertion_message); + + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_init_creds_password(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds, ctx->upn_dns, + "_none_", NULL, NULL, 0, + NULL, ctx->krb_options); + assertion_message = talloc_asprintf(ctx, + "krb5_get_init_creds_password(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u/%u,ok=%u]", + upn_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.error_io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.error_io, 1, assertion_message); + torture_assert_int_equal(tctx, KRB5_ERROR_CODE(&ctx->error), 68, assertion_message); + torture_assert(tctx, ctx->error.crealm != NULL, assertion_message); + torture_assert_str_equal(tctx, *ctx->error.crealm, trusted_realm_name, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert(tctx, ctx->error.cname != NULL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_type, KRB5_NT_ENTERPRISE_PRINCIPAL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_string.len, 1, assertion_message); + torture_assert_str_equal(tctx, ctx->error.cname->name_string.val[0], upn_dns_string, assertion_message); +#else + torture_assert(tctx, ctx->error.cname == NULL, assertion_message); +#endif + torture_assert_str_equal(tctx, ctx->error.realm, realm, assertion_message); + + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_init_creds_password(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds, ctx->upn_netbios, + "_none_", NULL, NULL, 0, + NULL, ctx->krb_options); + assertion_message = talloc_asprintf(ctx, + "krb5_get_init_creds_password(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u/%u,ok=%u]", + upn_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.error_io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.error_io, 1, assertion_message); + torture_assert_int_equal(tctx, KRB5_ERROR_CODE(&ctx->error), 68, assertion_message); + torture_assert(tctx, ctx->error.crealm != NULL, assertion_message); + torture_assert_str_equal(tctx, *ctx->error.crealm, trusted_realm_name, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert(tctx, ctx->error.cname != NULL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_type, KRB5_NT_ENTERPRISE_PRINCIPAL, assertion_message); + torture_assert_int_equal(tctx, ctx->error.cname->name_string.len, 1, assertion_message); + torture_assert_str_equal(tctx, ctx->error.cname->name_string.val[0], upn_netbios_string, assertion_message); +#else + torture_assert(tctx, ctx->error.cname == NULL, assertion_message); +#endif + torture_assert_str_equal(tctx, ctx->error.realm, realm, assertion_message); + + torture_comment(tctx, "(%s:%s) password[%s] old_password[%s]\n", + __location__, __FUNCTION__, + password, old_password); + if (old_password != NULL) { + k5ret = krb5_get_init_creds_password(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds, principal, + old_password, NULL, NULL, 0, + NULL, ctx->krb_options); + torture_assert_int_equal(tctx, k5ret, KRB5KDC_ERR_PREAUTH_FAILED, + "preauth should fail with old password"); + } + + k5ret = krb5_get_init_creds_password(ctx->smb_krb5_context->krb5_context, + &ctx->my_creds, principal, + password, NULL, NULL, 0, + NULL, ctx->krb_options); + if (k5ret == KRB5KDC_ERR_PREAUTH_FAILED) { + TALLOC_FREE(ctx); + return false; + } + + assertion_message = talloc_asprintf(ctx, + "krb5_get_init_creds_password for failed: %s", + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + torture_assert_int_equal(tctx, + krb5_get_creds_opt_alloc(ctx->smb_krb5_context->krb5_context, + &ctx->opt_canon), + 0, "krb5_get_creds_opt_alloc"); + + krb5_get_creds_opt_add_options(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + KRB5_GC_CANONICALIZE); + + krb5_get_creds_opt_add_options(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + KRB5_GC_NO_STORE); + + torture_assert_int_equal(tctx, + krb5_get_creds_opt_alloc(ctx->smb_krb5_context->krb5_context, + &ctx->opt_nocanon), + 0, "krb5_get_creds_opt_alloc"); + + krb5_get_creds_opt_add_options(ctx->smb_krb5_context->krb5_context, + ctx->opt_nocanon, + KRB5_GC_NO_STORE); + + krbtgt_cc_name = talloc_asprintf(ctx, "MEMORY:%p.krbtgt", ctx->smb_krb5_context); + torture_assert_int_equal(tctx, + krb5_cc_resolve(ctx->smb_krb5_context->krb5_context, + krbtgt_cc_name, + &ctx->krbtgt_ccache), + 0, "krb5_cc_resolve failed"); + + torture_assert_int_equal(tctx, + krb5_cc_initialize(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + ctx->my_creds.client), + 0, "krb5_cc_initialize failed"); + + torture_assert_int_equal(tctx, + krb5_cc_store_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + &ctx->my_creds), + 0, "krb5_cc_store_cred failed"); + + krbtgt_trust_realm_string = talloc_asprintf(ctx, "krbtgt/%s@%s", + trusted_realm_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_trust_realm, + realm, "krbtgt", + trusted_realm_name, NULL), + 0, "smb_krb5_make_principal failed"); + + krbtgt_trust_dns_string = talloc_asprintf(ctx, "krbtgt/%s@%s", + trusted_dns_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_trust_dns, + realm, "krbtgt", + trusted_dns_name, NULL), + 0, "smb_krb5_make_principal failed"); + + krbtgt_trust_netbios_string = talloc_asprintf(ctx, "krbtgt/%s@%s", + trusted_netbios_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_trust_netbios, + realm, "krbtgt", + trusted_netbios_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we can do a TGS for krbtgt/trusted_realm */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_nocanon, + ctx->krbtgt_ccache, + ctx->krbtgt_trust_realm, + &ctx->krbtgt_trust_realm_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_realm_creds->server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_realm_creds->server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Confirm if we have no referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); + + /* Confirm if we can do a TGS for krbtgt/trusted_dns with CANON */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->krbtgt_trust_dns, + &ctx->krbtgt_trust_dns_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + krbtgt_trust_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#endif + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + k5ret = decode_Ticket(ctx->krbtgt_referral_creds.ticket.data, + ctx->krbtgt_referral_creds.ticket.length, + &ctx->krbtgt_referral_ticket, NULL); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + if (kvno > 0) { + expected_kvno = kvno - 1; + } + if (ctx->krbtgt_referral_ticket.enc_part.kvno != NULL) { + t_kvno = *ctx->krbtgt_referral_ticket.enc_part.kvno; + assertion_message = talloc_asprintf(ctx, + "krbtgt_referral_ticket(%s) kvno(%u) expected(%u) current(%u)", + krbtgt_trust_realm_string, + (unsigned)t_kvno, (unsigned)expected_kvno,(unsigned)kvno); + torture_comment(tctx, "%s\n", assertion_message); + torture_assert_int_not_equal(tctx, t_kvno, 0, assertion_message); + } else { + assertion_message = talloc_asprintf(ctx, + "krbtgt_referral_ticket(%s) kvno(NULL) exptected(%u) current(%u)", + krbtgt_trust_realm_string, + (unsigned)expected_kvno,(unsigned)kvno); + torture_comment(tctx, "%s\n", assertion_message); + } + torture_assert_int_equal(tctx, t_kvno, expected_kvno, assertion_message); + + if (old_nthash != NULL && expected_kvno != kvno) { + torture_comment(tctx, "old_nthash: %s\n", assertion_message); + k5ret = smb_krb5_keyblock_init_contents(ctx->smb_krb5_context->krb5_context, + ENCTYPE_ARCFOUR_HMAC, + old_nthash->hash, + sizeof(old_nthash->hash), + &ctx->krbtgt_referral_keyblock); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + } else { + torture_comment(tctx, "nthash: %s\n", assertion_message); + k5ret = smb_krb5_keyblock_init_contents(ctx->smb_krb5_context->krb5_context, + ENCTYPE_ARCFOUR_HMAC, + nthash->hash, + sizeof(nthash->hash), + &ctx->krbtgt_referral_keyblock); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + } + k5ret = krb5_decrypt_ticket(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_ticket, + &ctx->krbtgt_referral_keyblock, + &ctx->krbtgt_referral_enc_part, + 0); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + /* Confirm if we can do a TGS for krbtgt/trusted_dns no CANON */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_nocanon, + ctx->krbtgt_ccache, + ctx->krbtgt_trust_dns, + &ctx->krbtgt_trust_dns_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s, nocanon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + krbtgt_trust_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#endif + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_dns_creds->server, +#ifdef USING_EMBEDDED_HEIMDAL + ctx->krbtgt_trust_dns); +#else + ctx->krbtgt_trust_realm); +#endif + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_dns_creds->server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + /* Confirm if we can do a TGS for krbtgt/NETBIOS with CANON */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->krbtgt_trust_netbios, + &ctx->krbtgt_trust_netbios_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s, canon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + krbtgt_trust_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#endif + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + /* Confirm if we can do a TGS for krbtgt/NETBIOS no CANON */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_nocanon, + ctx->krbtgt_ccache, + ctx->krbtgt_trust_netbios, + &ctx->krbtgt_trust_netbios_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s, nocanon) for failed: " + "(%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + krbtgt_trust_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#endif + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_netbios_creds->server, +#ifdef USING_EMBEDDED_HEIMDAL + ctx->krbtgt_trust_netbios); +#else + ctx->krbtgt_trust_realm); +#endif + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_trust_netbios_creds->server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + cifs_trust_dns_string = talloc_asprintf(ctx, "cifs/%s@%s", + trusted_dns_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->cifs_trust_dns, + realm, "cifs", + trusted_dns_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we get krbtgt/trusted_realm back when asking for cifs/trusted_realm */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->cifs_trust_dns, + &ctx->cifs_trust_dns_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s) for failed: (%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + cifs_trust_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#endif + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + cifs_trust_netbios_string = talloc_asprintf(ctx, "cifs/%s@%s", + trusted_netbios_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->cifs_trust_netbios, + realm, "cifs", + trusted_netbios_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we get krbtgt/trusted_realm back when asking for cifs/trusted_realm */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->cifs_trust_netbios, + &ctx->cifs_trust_netbios_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s) for failed: (%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + cifs_trust_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#endif + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + drs_trust_dns_string = talloc_asprintf(ctx, + "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s@%s", + workstation, trusted_dns_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->drs_trust_dns, + realm, "E3514235-4B06-11D1-AB04-00C04FC2DCD2", + workstation, trusted_dns_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we get krbtgt/trusted_realm back when asking for a 3 part principal */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->drs_trust_dns, + &ctx->drs_trust_dns_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s) for failed: (%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + drs_trust_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#endif + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + drs_trust_netbios_string = talloc_asprintf(ctx, + "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s@%s", + workstation, trusted_netbios_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->drs_trust_netbios, + realm, "E3514235-4B06-11D1-AB04-00C04FC2DCD2", + workstation, trusted_netbios_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we get krbtgt/trusted_realm back when asking for a 3 part principal */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->drs_trust_netbios, + &ctx->drs_trust_netbios_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s) for failed: (%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + drs_trust_netbios_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5_KDC_UNREACH, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 2, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.ok, 1, assertion_message); +#endif + + /* Confirm if we have the referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); +#else + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); + + k5ok = krb5_principal_compare(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server, + ctx->krbtgt_trust_realm); + torture_assert(tctx, k5ok, assertion_message); + type = smb_krb5_principal_get_type(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_referral_creds.server); + torture_assert_int_equal(tctx, type, KRB5_NT_SRV_INST, assertion_message); + + /* Delete the referral ticket from the cache */ + k5ret = krb5_cc_remove_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_remove_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, 0, assertion_message); +#endif + + four_trust_dns_string = talloc_asprintf(ctx, "four/tree/two/%s@%s", + trusted_dns_name, realm); + torture_assert_int_equal(tctx, + smb_krb5_make_principal(ctx->smb_krb5_context->krb5_context, + &ctx->four_trust_dns, + realm, "four", "tree", "two", + trusted_dns_name, NULL), + 0, "smb_krb5_make_principal failed"); + + /* Confirm if we get an error back for a 4 part principal */ + ZERO_STRUCT(ctx->counts); + k5ret = krb5_get_creds(ctx->smb_krb5_context->krb5_context, + ctx->opt_canon, + ctx->krbtgt_ccache, + ctx->four_trust_dns, + &ctx->four_trust_dns_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_get_creds(%s) for failed: (%d) %s; t[d=0x%x,t=0x%x,a=0x%x] [io=%u,error=%u,ok=%u]", + four_trust_dns_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx), + trusted->trust_direction, + trusted->trust_type, + trusted->trust_attributes, + ctx->counts.io, ctx->counts.errors, ctx->counts.ok); + torture_assert_int_equal(tctx, k5ret, KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, assertion_message); +#ifdef USING_EMBEDDED_HEIMDAL + torture_assert_int_equal(tctx, ctx->counts.io, 2, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.error_io, 2, assertion_message); +#else + torture_assert_int_equal(tctx, ctx->counts.io, 1, assertion_message); + torture_assert_int_equal(tctx, ctx->counts.error_io, 1, assertion_message); +#endif + torture_assert_int_equal(tctx, KRB5_ERROR_CODE(&ctx->error), 7, assertion_message); + + /* Confirm if we have no referral ticket in the cache */ + krb5_free_cred_contents(ctx->smb_krb5_context->krb5_context, + &ctx->krbtgt_referral_creds); + k5ret = krb5_cc_retrieve_cred(ctx->smb_krb5_context->krb5_context, + ctx->krbtgt_ccache, + 0, + ctx->krbtgt_trust_realm_creds, + &ctx->krbtgt_referral_creds); + assertion_message = talloc_asprintf(ctx, + "krb5_cc_retrieve_cred(%s) for failed: (%d) %s", + krbtgt_trust_realm_string, + k5ret, + smb_get_krb5_error_message(ctx->smb_krb5_context->krb5_context, + k5ret, ctx)); + torture_assert_int_equal(tctx, k5ret, KRB5_CC_END, assertion_message); + + TALLOC_FREE(ctx); + return true; +} +#endif + +static bool check_dom_trust_pw(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *our_netbios_name, + const char *our_dns_name, + enum netr_SchannelType secure_channel_type, + const struct lsa_TrustDomainInfoInfoEx *trusted, + const char *previous_password, + const char *current_password, + uint32_t current_version, + const char *next_password, + uint32_t next_version, + bool expected_result) +{ + struct cli_credentials *incoming_creds; + char *server_name = NULL; + char *account = NULL; + char *principal = NULL; + char *workstation = NULL; + const char *binding = torture_setting_string(tctx, "binding", NULL); + const char *host = torture_setting_string(tctx, "host", NULL); + const char *ip; + struct nbt_name nbt_name; + struct dcerpc_binding *b2; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword samr_crypt_password; + struct netr_CryptPassword netr_crypt_password; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; + struct netr_ServerPasswordSet2 s; + struct dcerpc_pipe *p1 = NULL; + struct dcerpc_pipe *p2 = NULL; + NTSTATUS status; + bool ok; + int rc; + const char *trusted_netbios_name = trusted->netbios_name.string; + const char *trusted_dns_name = trusted->domain_name.string; + struct tsocket_address *dest_addr; + struct cldap_socket *cldap; + struct cldap_netlogon cldap1; + + incoming_creds = cli_credentials_init(tctx); + torture_assert(tctx, incoming_creds, "cli_credentials_init"); + + cli_credentials_set_domain(incoming_creds, our_netbios_name, CRED_SPECIFIED); + cli_credentials_set_realm(incoming_creds, our_dns_name, CRED_SPECIFIED); + + if (secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + account = talloc_asprintf(tctx, "%s.", trusted_dns_name); + torture_assert(tctx, account, __location__); + + principal = talloc_asprintf(tctx, "%s$@%s", + trusted_netbios_name, + cli_credentials_get_realm(incoming_creds)); + torture_assert(tctx, principal, __location__); + + workstation = talloc_asprintf(tctx, "%sUP", + trusted_netbios_name); + torture_assert(tctx, workstation, __location__); + } else { + account = talloc_asprintf(tctx, "%s$", trusted_netbios_name); + torture_assert(tctx, account, __location__); + + workstation = talloc_asprintf(tctx, "%sDOWN", + trusted_netbios_name); + torture_assert(tctx, workstation, __location__); + } + + cli_credentials_set_username(incoming_creds, account, CRED_SPECIFIED); + if (principal != NULL) { + cli_credentials_set_principal(incoming_creds, principal, + CRED_SPECIFIED); + } + cli_credentials_set_kvno(incoming_creds, current_version); + cli_credentials_set_password(incoming_creds, current_password, CRED_SPECIFIED); + cli_credentials_set_old_password(incoming_creds, previous_password, CRED_SPECIFIED); + cli_credentials_set_workstation(incoming_creds, workstation, CRED_SPECIFIED); + cli_credentials_set_secure_channel_type(incoming_creds, secure_channel_type); + + make_nbt_name_server(&nbt_name, host); + + status = resolve_name_ex(lpcfg_resolve_context(tctx->lp_ctx), + 0, 0, &nbt_name, tctx, &ip, tctx->ev); + torture_assert_ntstatus_ok(tctx, status, + talloc_asprintf(tctx,"Failed to resolve %s: %s", + nbt_name.name, nt_errstr(status))); + + rc = tsocket_address_inet_from_strings(tctx, "ip", + ip, + lpcfg_cldap_port(tctx->lp_ctx), + &dest_addr); + torture_assert_int_equal(tctx, rc, 0, + talloc_asprintf(tctx, + "tsocket_address_inet_from_strings failed parsing %s:%d", + host, lpcfg_cldap_port(tctx->lp_ctx))); + + /* cldap_socket_init should now know about the dest. address */ + status = cldap_socket_init(tctx, NULL, dest_addr, &cldap); + torture_assert_ntstatus_ok(tctx, status, "cldap_socket_init"); + + ZERO_STRUCT(cldap1); + cldap1.in.dest_address = NULL; + cldap1.in.dest_port = 0; + cldap1.in.version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX; + cldap1.in.user = account; + if (secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + cldap1.in.acct_control = ACB_AUTOLOCK; + } else { + cldap1.in.acct_control = ACB_DOMTRUST; + } + status = cldap_netlogon(cldap, tctx, &cldap1); + torture_assert_ntstatus_ok(tctx, status, "cldap_netlogon"); + torture_assert_int_equal(tctx, cldap1.out.netlogon.ntver, + NETLOGON_NT_VERSION_5EX, + "ntver"); + torture_assert_int_equal(tctx, cldap1.out.netlogon.data.nt5_ex.nt_version, + NETLOGON_NT_VERSION_1 | NETLOGON_NT_VERSION_5EX, + "nt_version"); + torture_assert_int_equal(tctx, cldap1.out.netlogon.data.nt5_ex.command, + LOGON_SAM_LOGON_RESPONSE_EX, + "command"); + torture_assert_str_equal(tctx, cldap1.out.netlogon.data.nt5_ex.user_name, + cldap1.in.user, + "user_name"); + server_name = talloc_asprintf(tctx, "\\\\%s", + cldap1.out.netlogon.data.nt5_ex.pdc_dns_name); + torture_assert(tctx, server_name, __location__); + + status = dcerpc_parse_binding(tctx, binding, &b2); + torture_assert_ntstatus_ok(tctx, status, "Bad binding string"); + + status = dcerpc_pipe_connect_b(tctx, &p1, b2, + &ndr_table_netlogon, + cli_credentials_init_anon(tctx), + tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_pipe_connect_b"); + + ok = check_pw_with_ServerAuthenticate3(p1, tctx, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + server_name, + incoming_creds, &creds); + torture_assert_int_equal(tctx, ok, expected_result, + "check_pw_with_ServerAuthenticate3"); + if (expected_result == true) { + ok = test_SetupCredentialsPipe(p1, tctx, incoming_creds, creds, + DCERPC_SIGN | DCERPC_SEAL, &p2); + torture_assert_int_equal(tctx, ok, true, + "test_SetupCredentialsPipe"); + } + TALLOC_FREE(p1); + + if (trusted->trust_type != LSA_TRUST_TYPE_DOWNLEVEL) { +#ifdef SAMBA4_USES_HEIMDAL + ok = check_pw_with_krb5(tctx, incoming_creds, trusted); + torture_assert_int_equal(tctx, ok, expected_result, + "check_pw_with_krb5"); +#else + torture_comment(tctx, "skipping check_pw_with_krb5 for MIT Kerberos build"); +#endif + } + + if (expected_result != true || next_password == NULL) { + TALLOC_FREE(p2); + return true; + } + + /* + * netr_ServerPasswordSet2 + */ + ok = encode_pw_buffer(samr_crypt_password.data, + next_password, STR_UNICODE); + torture_assert(tctx, ok, "encode_pw_buffer"); + + if (next_version != 0) { + struct NL_PASSWORD_VERSION version; + uint32_t len = IVAL(samr_crypt_password.data, 512); + uint32_t ofs = 512 - len; + uint8_t *ptr; + + ofs -= 12; + + version.ReservedField = 0; + version.PasswordVersionNumber = next_version; + version.PasswordVersionPresent = + NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT; + + ptr = samr_crypt_password.data + ofs; + SIVAL(ptr, 0, version.ReservedField); + SIVAL(ptr, 4, version.PasswordVersionNumber); + SIVAL(ptr, 8, version.PasswordVersionPresent); + } + + netlogon_creds_client_authenticator(creds, &req_auth); + ZERO_STRUCT(rep_auth); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, + samr_crypt_password.data, + 516); + } else { + netlogon_creds_arcfour_crypt(creds, + samr_crypt_password.data, + 516); + } + + memcpy(netr_crypt_password.data, + samr_crypt_password.data, 512); + netr_crypt_password.length = IVAL(samr_crypt_password.data, 512); + + + s.in.server_name = server_name; + s.in.account_name = cli_credentials_get_username(incoming_creds); + s.in.secure_channel_type = cli_credentials_get_secure_channel_type(incoming_creds); + s.in.computer_name = cli_credentials_get_workstation(incoming_creds); + s.in.credential = &req_auth; + s.in.new_password = &netr_crypt_password; + s.out.return_authenticator = &rep_auth; + status = dcerpc_netr_ServerPasswordSet2_r(p2->binding_handle, tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "failed to set password"); + + ok = netlogon_creds_client_check(creds, &rep_auth.cred); + torture_assert(tctx, ok, "netlogon_creds_client_check"); + + cli_credentials_set_kvno(incoming_creds, next_version); + cli_credentials_set_password(incoming_creds, next_password, CRED_SPECIFIED); + cli_credentials_set_old_password(incoming_creds, current_password, CRED_SPECIFIED); + + TALLOC_FREE(p2); + status = dcerpc_pipe_connect_b(tctx, &p2, b2, + &ndr_table_netlogon, + cli_credentials_init_anon(tctx), + tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_pipe_connect_b"); + + ok = check_pw_with_ServerAuthenticate3(p2, tctx, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + server_name, + incoming_creds, &creds); + torture_assert(tctx, ok, "check_pw_with_ServerAuthenticate3 with changed password"); + + if (trusted->trust_type != LSA_TRUST_TYPE_DOWNLEVEL) { +#if SAMBA4_USES_HEIMDAL + ok = check_pw_with_krb5(tctx, incoming_creds, trusted); + torture_assert(tctx, ok, "check_pw_with_krb5 with changed password"); +#else + torture_comment(tctx, "skipping check_pw_with_krb5 for MIT Kerberos build"); +#endif + } + + TALLOC_FREE(p2); + return true; +} + +static bool test_CreateTrustedDomainEx_common(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_trusts, + bool ex2_call) +{ + NTSTATUS status; + bool ret = true; + struct lsa_QueryInfoPolicy2 p2; + union lsa_PolicyInformation *our_info = NULL; + struct lsa_CreateTrustedDomainEx r; + struct lsa_CreateTrustedDomainEx2 r2; + struct lsa_TrustDomainInfoInfoEx trustinfo; + struct lsa_TrustDomainInfoAuthInfoInternal *authinfo_internal = NULL; + struct lsa_TrustDomainInfoAuthInfo *authinfo = NULL; + struct dom_sid **domsid; + struct policy_handle *trustdom_handle; + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info = NULL; + DATA_BLOB session_key; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *id; + const char *incoming_v00 = TRUSTPW "InV00"; + const char *incoming_v0 = TRUSTPW "InV0"; + const char *incoming_v1 = TRUSTPW "InV1"; + const char *incoming_v2 = TRUSTPW "InV2"; + const char *incoming_v40 = TRUSTPW "InV40"; + const char *outgoing_v00 = TRUSTPW "OutV00"; + const char *outgoing_v0 = TRUSTPW "OutV0"; + + if (ex2_call) { + torture_comment(tctx, "\nTesting CreateTrustedDomainEx2 for %d domains\n", num_trusts); + id = "3"; + } else { + torture_comment(tctx, "\nTesting CreateTrustedDomainEx for %d domains\n", num_trusts); + id = "2"; + } + + domsid = talloc_array(tctx, struct dom_sid *, num_trusts); + trustdom_handle = talloc_array(tctx, struct policy_handle, num_trusts); + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed - %s\n", nt_errstr(status)); + return false; + } + + ZERO_STRUCT(p2); + p2.in.handle = handle; + p2.in.level = LSA_POLICY_INFO_DNS; + p2.out.info = &our_info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_QueryInfoPolicy2_r(b, tctx, &p2), + "lsa_QueryInfoPolicy2 failed"); + torture_assert_ntstatus_ok(tctx, p2.out.result, + "lsa_QueryInfoPolicy2 failed"); + torture_assert(tctx, our_info != NULL, "lsa_QueryInfoPolicy2 our_info"); + + for (i=0; i< num_trusts; i++) { + char *trust_name = talloc_asprintf(tctx, "TORTURE%s%02d", id, i); + char *trust_name_dns = talloc_asprintf(tctx, "torturedom%s%02d.samba._none_.example.com", id, i); + char *trust_sid = talloc_asprintf(tctx, "S-1-5-21-97398-379795-%s%02d", id, i); + bool ok; + + domsid[i] = dom_sid_parse_talloc(tctx, trust_sid); + + trustinfo.sid = domsid[i]; + trustinfo.netbios_name.string = trust_name; + trustinfo.domain_name.string = trust_name_dns; + + /* Create inbound, some outbound, and some + * bi-directional trusts in a repeating pattern based + * on i */ + + /* 1 == inbound, 2 == outbound, 3 == both */ + trustinfo.trust_direction = (i % 3) + 1; + + /* Try different trust types too */ + + /* 1 == downlevel (NT4), 2 == uplevel (ADS), 3 == MIT (kerberos but not AD) */ + trustinfo.trust_type = (((i / 3) + 1) % 3) + 1; + + trustinfo.trust_attributes = LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION; + + ok = gen_authinfo_internal(tctx, incoming_v00, incoming_v0, + outgoing_v00, outgoing_v0, + session_key, &authinfo_internal); + if (!ok) { + torture_comment(tctx, "gen_authinfo_internal failed"); + ret = false; + } + + ok = gen_authinfo(tctx, incoming_v00, incoming_v0, + outgoing_v00, outgoing_v0, + &authinfo); + if (!ok) { + torture_comment(tctx, "gen_authinfonfo failed"); + ret = false; + } + + if (ex2_call) { + + r2.in.policy_handle = handle; + r2.in.info = &trustinfo; + r2.in.auth_info_internal = authinfo_internal; + r2.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r2.out.trustdom_handle = &trustdom_handle[i]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_CreateTrustedDomainEx2_r(b, tctx, &r2), + "CreateTrustedDomainEx2 failed"); + + status = r2.out.result; + } else { + + r.in.policy_handle = handle; + r.in.info = &trustinfo; + r.in.auth_info = authinfo; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.trustdom_handle = &trustdom_handle[i]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_CreateTrustedDomainEx_r(b, tctx, &r), + "CreateTrustedDomainEx failed"); + + status = r.out.result; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { + test_DeleteTrustedDomain(b, tctx, handle, trustinfo.netbios_name); + if (ex2_call) { + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_CreateTrustedDomainEx2_r(b, tctx, &r2), + "CreateTrustedDomainEx2 failed"); + status = r2.out.result; + } else { + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_CreateTrustedDomainEx_r(b, tctx, &r), + "CreateTrustedDomainEx2 failed"); + status = r.out.result; + } + } + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "CreateTrustedDomainEx failed2 - %s\n", nt_errstr(status)); + ret = false; + } else { + /* For outbound and MIT trusts there is no trust account */ + if (trustinfo.trust_direction != 2 && + trustinfo.trust_type != 3) { + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_comment(tctx, "skipping trusted domain auth tests against samba3\n"); + } else if (ex2_call == false && + torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping CreateTrustedDomainEx trusted domain auth tests against samba4\n"); + + } else { + ok = check_dom_trust_pw(p, tctx, + our_info->dns.name.string, + our_info->dns.dns_domain.string, + SEC_CHAN_DOMAIN, + &trustinfo, + NULL, + "x" TRUSTPW "x", 0, + NULL, 0, + false); + if (!ok) { + torture_comment(tctx, "Password check passed unexpectedly\n"); + ret = false; + } + ok = check_dom_trust_pw(p, tctx, + our_info->dns.name.string, + our_info->dns.dns_domain.string, + SEC_CHAN_DOMAIN, + &trustinfo, + incoming_v00, + incoming_v0, 0, + incoming_v1, 1, + true); + if (!ok) { + torture_comment(tctx, "Password check failed (SEC_CHAN_DOMAIN)\n"); + ret = false; + } + ok = check_dom_trust_pw(p, tctx, + our_info->dns.name.string, + our_info->dns.dns_domain.string, + SEC_CHAN_DNS_DOMAIN, + &trustinfo, + incoming_v0, + incoming_v1, 1, + incoming_v2, 2, + true); + if (!ok) { + torture_comment(tctx, "Password check failed v2 (SEC_CHAN_DNS_DOMAIN)\n"); + ret = false; + } + ok = check_dom_trust_pw(p, tctx, + our_info->dns.name.string, + our_info->dns.dns_domain.string, + SEC_CHAN_DNS_DOMAIN, + &trustinfo, + incoming_v1, + incoming_v2, 2, + incoming_v40, 40, + true); + if (!ok) { + torture_comment(tctx, "Password check failed v4 (SEC_CHAN_DNS_DOMAIN)\n"); + ret = false; + } + } + } + + q.in.trustdom_handle = &trustdom_handle[i]; + q.in.level = LSA_TRUSTED_DOMAIN_INFO_INFO_EX; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfo_r(b, tctx, &q), + "QueryTrustedDomainInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_comment(tctx, "QueryTrustedDomainInfo level 1 failed - %s\n", nt_errstr(q.out.result)); + ret = false; + } else if (!q.out.info) { + torture_comment(tctx, "QueryTrustedDomainInfo level 1 failed to return an info pointer\n"); + ret = false; + } else { + if (strcmp(info->info_ex.domain_name.string, trustinfo.domain_name.string) != 0) { + torture_comment(tctx, "QueryTrustedDomainInfo returned inconsistent long name: %s != %s\n", + info->info_ex.domain_name.string, trustinfo.domain_name.string); + ret = false; + } + if (strcmp(info->info_ex.netbios_name.string, trustinfo.netbios_name.string) != 0) { + torture_comment(tctx, "QueryTrustedDomainInfo returned inconsistent short name: %s != %s\n", + info->info_ex.netbios_name.string, trustinfo.netbios_name.string); + ret = false; + } + if (info->info_ex.trust_type != trustinfo.trust_type) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust type %d != %d\n", + trust_name, info->info_ex.trust_type, trustinfo.trust_type); + ret = false; + } + if (info->info_ex.trust_attributes != LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust attributes %d != %d\n", + trust_name, info->info_ex.trust_attributes, LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION); + ret = false; + } + if (info->info_ex.trust_direction != trustinfo.trust_direction) { + torture_comment(tctx, "QueryTrustedDomainInfo of %s returned incorrect trust direction %d != %d\n", + trust_name, info->info_ex.trust_direction, trustinfo.trust_direction); + ret = false; + } + } + } + } + + /* now that we have some domains to look over, we can test the enum calls */ + if (!test_EnumTrustDom(b, tctx, handle)) { + torture_comment(tctx, "test_EnumTrustDom failed\n"); + ret = false; + } + + if (!test_EnumTrustDomEx(b, tctx, handle)) { + torture_comment(tctx, "test_EnumTrustDomEx failed\n"); + ret = false; + } + + for (i=0; i<num_trusts; i++) { + if (!test_DeleteTrustedDomainBySid(b, tctx, handle, domsid[i])) { + torture_comment(tctx, "test_DeleteTrustedDomainBySid failed\n"); + ret = false; + } + } + + return ret; +} + +static bool test_CreateTrustedDomainEx2(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_trusts) +{ + return test_CreateTrustedDomainEx_common(p, tctx, handle, num_trusts, true); +} + +static bool test_CreateTrustedDomainEx(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_trusts) +{ + return test_CreateTrustedDomainEx_common(p, tctx, handle, num_trusts, false); +} + +static bool test_QueryDomainInfoPolicy(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_QueryDomainInformationPolicy r; + union lsa_DomainInformationPolicy *info = NULL; + int i; + bool ret = true; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping QueryDomainInformationPolicy test\n"); + } + + torture_comment(tctx, "\nTesting QueryDomainInformationPolicy\n"); + + for (i=2;i<4;i++) { + r.in.handle = handle; + r.in.level = i; + r.out.info = &info; + + torture_comment(tctx, "\nTrying QueryDomainInformationPolicy level %d\n", i); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryDomainInformationPolicy_r(b, tctx, &r), + "QueryDomainInformationPolicy failed"); + + /* If the server does not support EFS, then this is the correct return */ + if (i == LSA_DOMAIN_INFO_POLICY_EFS && NT_STATUS_EQUAL(r.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + continue; + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "QueryDomainInformationPolicy failed - %s\n", nt_errstr(r.out.result)); + ret = false; + continue; + } + } + + return ret; +} + + +static bool test_QueryInfoPolicyCalls( bool version2, + struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_QueryInfoPolicy r; + union lsa_PolicyInformation *info = NULL; + int i; + bool ret = true; + const char *call = talloc_asprintf(tctx, "QueryInfoPolicy%s", version2 ? "2":""); + + torture_comment(tctx, "\nTesting %s\n", call); + + if (version2 && torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping QueryInfoPolicy2 tests\n"); + } + + for (i=1;i<=14;i++) { + r.in.handle = handle; + r.in.level = i; + r.out.info = &info; + + torture_comment(tctx, "\nTrying %s level %d\n", call, i); + + if (version2) + /* We can perform the cast, because both types are + structurally equal */ + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryInfoPolicy2_r(b, tctx, + (struct lsa_QueryInfoPolicy2*) &r), + "QueryInfoPolicy2 failed"); + else + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryInfoPolicy_r(b, tctx, &r), + "QueryInfoPolicy2 failed"); + + switch (i) { + case LSA_POLICY_INFO_MOD: + case LSA_POLICY_INFO_AUDIT_FULL_SET: + case LSA_POLICY_INFO_AUDIT_FULL_QUERY: + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_comment(tctx, "Server should have failed level %u: %s\n", i, nt_errstr(r.out.result)); + ret = false; + } + break; + case LSA_POLICY_INFO_DOMAIN: + case LSA_POLICY_INFO_ACCOUNT_DOMAIN: + case LSA_POLICY_INFO_REPLICA: + case LSA_POLICY_INFO_QUOTA: + case LSA_POLICY_INFO_ROLE: + case LSA_POLICY_INFO_AUDIT_LOG: + case LSA_POLICY_INFO_AUDIT_EVENTS: + case LSA_POLICY_INFO_PD: + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "%s failed - %s\n", call, nt_errstr(r.out.result)); + ret = false; + } + break; + case LSA_POLICY_INFO_L_ACCOUNT_DOMAIN: + case LSA_POLICY_INFO_DNS_INT: + case LSA_POLICY_INFO_DNS: + if (torture_setting_bool(tctx, "samba3", false)) { + /* Other levels not implemented yet */ + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_INFO_CLASS)) { + torture_comment(tctx, "%s failed - %s\n", call, nt_errstr(r.out.result)); + ret = false; + } + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "%s failed - %s\n", call, nt_errstr(r.out.result)); + ret = false; + } + break; + default: + if (torture_setting_bool(tctx, "samba4", false)) { + /* Other levels not implemented yet */ + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_INFO_CLASS)) { + torture_comment(tctx, "%s failed - %s\n", call, nt_errstr(r.out.result)); + ret = false; + } + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "%s failed - %s\n", call, nt_errstr(r.out.result)); + ret = false; + } + break; + } + + if (NT_STATUS_IS_OK(r.out.result) && (i == LSA_POLICY_INFO_DNS + || i == LSA_POLICY_INFO_DNS_INT)) { + /* Let's look up some of these names */ + + struct lsa_TransNameArray tnames, dnames; + tnames.count = 14; + tnames.names = talloc_zero_array(tctx, struct lsa_TranslatedName, tnames.count); + tnames.names[0].name.string = info->dns.name.string; + tnames.names[0].sid_type = SID_NAME_DOMAIN; + tnames.names[1].name.string = info->dns.dns_domain.string; + tnames.names[1].sid_type = SID_NAME_DOMAIN; + tnames.names[2].name.string = talloc_asprintf(tctx, "%s\\", info->dns.name.string); + tnames.names[2].sid_type = SID_NAME_DOMAIN; + tnames.names[3].name.string = talloc_asprintf(tctx, "%s\\", info->dns.dns_domain.string); + tnames.names[3].sid_type = SID_NAME_DOMAIN; + tnames.names[4].name.string = talloc_asprintf(tctx, "%s\\guest", info->dns.name.string); + tnames.names[4].sid_type = SID_NAME_USER; + tnames.names[5].name.string = talloc_asprintf(tctx, "%s\\krbtgt", info->dns.name.string); + tnames.names[5].sid_type = SID_NAME_USER; + tnames.names[6].name.string = talloc_asprintf(tctx, "%s\\guest", info->dns.dns_domain.string); + tnames.names[6].sid_type = SID_NAME_USER; + tnames.names[7].name.string = talloc_asprintf(tctx, "%s\\krbtgt", info->dns.dns_domain.string); + tnames.names[7].sid_type = SID_NAME_USER; + tnames.names[8].name.string = talloc_asprintf(tctx, "krbtgt@%s", info->dns.name.string); + tnames.names[8].sid_type = SID_NAME_USER; + tnames.names[9].name.string = talloc_asprintf(tctx, "krbtgt@%s", info->dns.dns_domain.string); + tnames.names[9].sid_type = SID_NAME_USER; + tnames.names[10].name.string = talloc_asprintf(tctx, "%s\\"TEST_MACHINENAME "$", info->dns.name.string); + tnames.names[10].sid_type = SID_NAME_USER; + tnames.names[11].name.string = talloc_asprintf(tctx, "%s\\"TEST_MACHINENAME "$", info->dns.dns_domain.string); + tnames.names[11].sid_type = SID_NAME_USER; + tnames.names[12].name.string = talloc_asprintf(tctx, TEST_MACHINENAME "$@%s", info->dns.name.string); + tnames.names[12].sid_type = SID_NAME_USER; + tnames.names[13].name.string = talloc_asprintf(tctx, TEST_MACHINENAME "$@%s", info->dns.dns_domain.string); + tnames.names[13].sid_type = SID_NAME_USER; + ret &= test_LookupNames(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &tnames); + + /* Try to use in-forest search for the test machine */ + dnames.count = 1; + dnames.names = talloc_zero_array(tctx, struct lsa_TranslatedName, dnames.count); + dnames.names[0].name.string = talloc_asprintf(tctx, "%s\\"TEST_MACHINENAME "$", info->dns.name.string); + dnames.names[0].sid_type = SID_NAME_USER; + ret &= test_LookupNames(b, tctx, handle, LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2, &dnames); + } + } + + return ret; +} + +static bool test_QueryInfoPolicy(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + return test_QueryInfoPolicyCalls(false, b, tctx, handle); +} + +static bool test_QueryInfoPolicy2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + return test_QueryInfoPolicyCalls(true, b, tctx, handle); +} + +static bool test_GetUserName(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct lsa_GetUserName r; + struct lsa_String *authority_name_p = NULL; + struct lsa_String *account_name_p = NULL; + + torture_comment(tctx, "\nTesting GetUserName\n"); + + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = NULL; + r.out.account_name = &account_name_p; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetUserName_r(b, tctx, &r), + "GetUserName failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "GetUserName result failed"); + torture_assert_not_null(tctx, r.out.account_name, "r.out.account_name"); + torture_assert_not_null(tctx, *r.out.account_name, "*r.out.account_name"); + torture_assert(tctx, r.out.authority_name == NULL, "r.out.authority_name"); + + account_name_p = NULL; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetUserName_r(b, tctx, &r), + "GetUserName failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "GetUserName result failed"); + torture_assert_not_null(tctx, r.out.account_name, "r.out.account_name"); + torture_assert_not_null(tctx, *r.out.account_name, "*r.out.account_name"); + torture_assert_not_null(tctx, r.out.authority_name, "r.out.authority_name"); + torture_assert_not_null(tctx, *r.out.authority_name, "*r.out.authority_name"); + + torture_comment(tctx, + "Account Name: %s, Authority Name: %s\n", + (*r.out.account_name)->string, + (*r.out.authority_name)->string); + + return true; +} + +static bool test_GetUserName_fail(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct lsa_GetUserName r; + struct lsa_String *account_name_p = NULL; + NTSTATUS status; + + torture_comment(tctx, "\nTesting GetUserName_fail\n"); + + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = NULL; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, + "GetUserName correctly returned with " + "status: %s\n", + nt_errstr(status)); + return true; + } + + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_ACCESS_DENIED, + "GetUserName return value should " + "be ACCESS_DENIED"); + return true; + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(r.out.result, NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED)) { + torture_comment(tctx, + "GetUserName correctly returned with " + "result: %s\n", + nt_errstr(r.out.result)); + return true; + } + } + + torture_assert_ntstatus_equal(tctx, + r.out.result, + NT_STATUS_OK, + "GetUserName return value should be " + "ACCESS_DENIED"); + + return false; +} + +bool test_lsa_Close(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct lsa_Close r; + struct policy_handle handle2; + + torture_comment(tctx, "\nTesting Close\n"); + + r.in.handle = handle; + r.out.handle = &handle2; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_Close_r(b, tctx, &r), + "Close failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Close failed"); + + torture_assert_ntstatus_equal(tctx, dcerpc_lsa_Close_r(b, tctx, &r), + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, "Close should failed"); + + torture_comment(tctx, "\n"); + + return true; +} + +bool torture_rpc_lsa(struct torture_context *tctx) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct policy_handle *handle = NULL; + struct test_join *join = NULL; + struct cli_credentials *machine_creds; + struct dcerpc_binding_handle *b; + enum dcerpc_transport_t transport; + + status = torture_rpc_connection(tctx, &p, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + b = p->binding_handle; + transport = dcerpc_binding_get_transport(p->binding); + + /* Test lsaLookupSids3 and lsaLookupNames4 over tcpip */ + if (transport == NCACN_IP_TCP) { + if (!test_OpenPolicy_fail(b, tctx)) { + ret = false; + } + + if (!test_OpenPolicy2_fail(b, tctx)) { + ret = false; + } + + if (!test_many_LookupSids(p, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + return ret; + } + + if (!test_OpenPolicy(b, tctx)) { + ret = false; + } + + if (!test_lsa_OpenPolicy2(b, tctx, &handle)) { + ret = false; + } + + if (handle) { + join = torture_join_domain(tctx, TEST_MACHINENAME, ACB_WSTRUST, &machine_creds); + if (!join) { + ret = false; + } + + if (!test_LookupSids_async(b, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + if (!test_QueryDomainInfoPolicy(b, tctx, handle)) { + ret = false; + } + + if (!test_CreateSecret(p, tctx, handle)) { + ret = false; + } + + if (!test_QueryInfoPolicy(b, tctx, handle)) { + ret = false; + } + + if (!test_QueryInfoPolicy2(b, tctx, handle)) { + ret = false; + } + + if (!test_Delete(b, tctx, handle)) { + ret = false; + } + + if (!test_many_LookupSids(p, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + if (!test_lsa_Close(b, tctx, handle)) { + ret = false; + } + + torture_leave_domain(tctx, join); + + } else { + if (!test_many_LookupSids(p, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + } + + if (!test_GetUserName(b, tctx)) { + ret = false; + } + + return ret; +} + +bool torture_rpc_lsa_get_user(struct torture_context *tctx) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct dcerpc_binding_handle *b; + enum dcerpc_transport_t transport; + + status = torture_rpc_connection(tctx, &p, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + b = p->binding_handle; + transport = dcerpc_binding_get_transport(p->binding); + + if (transport == NCACN_IP_TCP) { + if (!test_GetUserName_fail(b, tctx)) { + ret = false; + } + return ret; + } + + if (!test_GetUserName(b, tctx)) { + ret = false; + } + + return ret; +} + +static bool testcase_LookupNames(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + bool ret = true; + struct policy_handle *handle; + struct lsa_TransNameArray tnames; + struct lsa_TransNameArray2 tnames2; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(p->binding); + + if (transport != NCACN_NP && transport != NCALRPC) { + torture_comment(tctx, "testcase_LookupNames is only available " + "over NCACN_NP or NCALRPC"); + return true; + } + + if (!test_OpenPolicy(b, tctx)) { + ret = false; + } + + if (!test_lsa_OpenPolicy2(b, tctx, &handle)) { + ret = false; + } + + if (!handle) { + ret = false; + } + + tnames.count = 1; + tnames.names = talloc_array(tctx, struct lsa_TranslatedName, tnames.count); + ZERO_STRUCT(tnames.names[0]); + tnames.names[0].name.string = "BUILTIN"; + tnames.names[0].sid_type = SID_NAME_DOMAIN; + + if (!test_LookupNames(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &tnames)) { + ret = false; + } + + tnames2.count = 1; + tnames2.names = talloc_array(tctx, struct lsa_TranslatedName2, tnames2.count); + ZERO_STRUCT(tnames2.names[0]); + tnames2.names[0].name.string = "BUILTIN"; + tnames2.names[0].sid_type = SID_NAME_DOMAIN; + + if (!test_LookupNames2(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &tnames2, true)) { + ret = false; + } + + if (!test_LookupNames3(b, tctx, handle, LSA_LOOKUP_NAMES_ALL, &tnames2, true)) { + ret = false; + } + + if (!test_LookupNames_wellknown(b, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + if (!test_LookupNames_NULL(b, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + if (!test_LookupNames_bogus(b, tctx, handle, LSA_LOOKUP_NAMES_ALL)) { + ret = false; + } + + if (!test_lsa_Close(b, tctx, handle)) { + ret = false; + } + + return ret; +} + +struct torture_suite *torture_rpc_lsa_lookup_names(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + + suite = torture_suite_create(mem_ctx, "lsa.lookupnames"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "lsa", + &ndr_table_lsarpc); + torture_rpc_tcase_add_test(tcase, "LookupNames", + testcase_LookupNames); + + return suite; +} + +struct lsa_trustdom_state { + uint32_t num_trusts; +}; + +static bool testcase_TrustedDomains(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + bool ret = true; + struct policy_handle *handle; + struct lsa_trustdom_state *state = + talloc_get_type_abort(data, struct lsa_trustdom_state); + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(p->binding); + + if (transport != NCACN_NP && transport != NCALRPC) { + torture_comment(tctx, "testcase_TrustedDomains is only available " + "over NCACN_NP or NCALRPC"); + return true; + } + + torture_comment(tctx, "Testing %d domains\n", state->num_trusts); + + if (!test_OpenPolicy(b, tctx)) { + ret = false; + } + + if (!test_lsa_OpenPolicy2(b, tctx, &handle)) { + ret = false; + } + + if (!handle) { + ret = false; + } + + if (!test_CreateTrustedDomain(b, tctx, handle, state->num_trusts)) { + ret = false; + } + + if (!test_CreateTrustedDomainEx(p, tctx, handle, state->num_trusts)) { + ret = false; + } + + if (!test_CreateTrustedDomainEx2(p, tctx, handle, state->num_trusts)) { + ret = false; + } + + if (!test_lsa_Close(b, tctx, handle)) { + ret = false; + } + + return ret; +} + +struct torture_suite *torture_rpc_lsa_trusted_domains(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + struct lsa_trustdom_state *state; + + state = talloc(mem_ctx, struct lsa_trustdom_state); + + state->num_trusts = 12; + + suite = torture_suite_create(mem_ctx, "lsa.trusted.domains"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "lsa", + &ndr_table_lsarpc); + torture_rpc_tcase_add_test_ex(tcase, "TrustedDomains", + testcase_TrustedDomains, + state); + + return suite; +} + +static bool testcase_Privileges(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct policy_handle *handle; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(p->binding); + + if (transport != NCACN_NP && transport != NCALRPC) { + torture_skip(tctx, "testcase_Privileges is only available " + "over NCACN_NP or NCALRPC"); + } + + if (!test_OpenPolicy(b, tctx)) { + return false; + } + + if (!test_lsa_OpenPolicy2(b, tctx, &handle)) { + return false; + } + + if (!handle) { + return false; + } + + if (!test_CreateAccount(b, tctx, handle)) { + return false; + } + + if (!test_EnumAccounts(b, tctx, handle)) { + return false; + } + + if (!test_EnumPrivs(b, tctx, handle)) { + return false; + } + + if (!test_lsa_Close(b, tctx, handle)) { + return false; + } + + return true; +} + + +struct torture_suite *torture_rpc_lsa_privileges(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + + suite = torture_suite_create(mem_ctx, "lsa.privileges"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "lsa", + &ndr_table_lsarpc); + torture_rpc_tcase_add_test(tcase, "Privileges", + testcase_Privileges); + + return suite; +} diff --git a/source4/torture/rpc/lsa_lookup.c b/source4/torture/rpc/lsa_lookup.c new file mode 100644 index 0000000..f641827 --- /dev/null +++ b/source4/torture/rpc/lsa_lookup.c @@ -0,0 +1,428 @@ +/* + Unix SMB/CIFS implementation. + test suite for lsa rpc lookup operations + + Copyright (C) Volker Lendecke 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 "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "libcli/security/security.h" + +static bool open_policy(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle **handle) +{ + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + + *handle = talloc(tctx, struct policy_handle); + if (!*handle) { + return false; + } + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = *handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenPolicy2_r(b, tctx, &r), + "OpenPolicy2 failed"); + + return NT_STATUS_IS_OK(r.out.result); +} + +static bool get_domainsid(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct dom_sid **sid) +{ + struct lsa_QueryInfoPolicy r; + union lsa_PolicyInformation *info = NULL; + + r.in.level = LSA_POLICY_INFO_DOMAIN; + r.in.handle = handle; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryInfoPolicy_r(b, tctx, &r), + "QueryInfoPolicy failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "QueryInfoPolicy failed"); + + *sid = info->domain.sid; + return true; +} + +static NTSTATUS lookup_sids(struct torture_context *tctx, + uint16_t level, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct dom_sid **sids, uint32_t num_sids, + struct lsa_TransNameArray *names) +{ + struct lsa_LookupSids r; + struct lsa_SidArray sidarray; + struct lsa_RefDomainList *domains; + uint32_t count = 0; + uint32_t i; + NTSTATUS status; + + names->count = 0; + names->names = NULL; + + sidarray.num_sids = num_sids; + sidarray.sids = talloc_array(tctx, struct lsa_SidPtr, num_sids); + + for (i=0; i<num_sids; i++) { + sidarray.sids[i].sid = sids[i]; + } + + r.in.handle = handle; + r.in.sids = &sidarray; + r.in.names = names; + r.in.level = level; + r.in.count = &count; + r.out.names = names; + r.out.count = &count; + r.out.domains = &domains; + + status = dcerpc_lsa_LookupSids_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return r.out.result; +} + +static bool test_lookupsids(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct dom_sid **sids, uint32_t num_sids, + int level, NTSTATUS expected_result, + enum lsa_SidType *types) +{ + struct lsa_TransNameArray names; + NTSTATUS status; + uint32_t i; + bool ret = true; + + status = lookup_sids(tctx, level, b, handle, sids, num_sids, + &names); + if (!NT_STATUS_EQUAL(status, expected_result)) { + printf("For level %d expected %s, got %s\n", + level, nt_errstr(expected_result), + nt_errstr(status)); + return false; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK) && + !NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) { + return true; + } + + for (i=0; i<num_sids; i++) { + if (names.names[i].sid_type != types[i]) { + printf("In level %d, for sid %s expected %s, " + "got %s\n", level, + dom_sid_string(tctx, sids[i]), + sid_type_lookup(types[i]), + sid_type_lookup(names.names[i].sid_type)); + ret = false; + } + } + return ret; +} + +static bool get_downleveltrust(struct torture_context *tctx, struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct dom_sid **sid) +{ + struct lsa_EnumTrustDom r; + uint32_t resume_handle = 0; + struct lsa_DomainList domains; + int i; + + r.in.handle = handle; + r.in.resume_handle = &resume_handle; + r.in.max_size = 1000; + r.out.domains = &domains; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustDom_r(b, tctx, &r), + "EnumTrustDom failed"); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NO_MORE_ENTRIES)) + torture_fail(tctx, "no trusts"); + + if (domains.count == 0) { + torture_fail(tctx, "no trusts"); + } + + for (i=0; i<domains.count; i++) { + struct lsa_QueryTrustedDomainInfoBySid q; + union lsa_TrustedDomainInfo *info = NULL; + + if (domains.domains[i].sid == NULL) + continue; + + q.in.handle = handle; + q.in.dom_sid = domains.domains[i].sid; + q.in.level = 6; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QueryTrustedDomainInfoBySid_r(b, tctx, &q), + "QueryTrustedDomainInfoBySid failed"); + if (!NT_STATUS_IS_OK(q.out.result)) continue; + + if ((info->info_ex.trust_direction & 2) && + (info->info_ex.trust_type == 1)) { + *sid = domains.domains[i].sid; + return true; + } + } + + torture_fail(tctx, "I need a AD DC with an outgoing trust to NT4"); +} + +#define NUM_SIDS 8 + +bool torture_rpc_lsa_lookup(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct policy_handle *handle; + struct dom_sid *dom_sid = NULL; + struct dom_sid *trusted_sid = NULL; + struct dom_sid *sids[NUM_SIDS]; + struct dcerpc_binding_handle *b; + enum dcerpc_transport_t transport; + + status = torture_rpc_connection(torture, &p, &ndr_table_lsarpc); + if (!NT_STATUS_IS_OK(status)) { + torture_fail(torture, "unable to connect to table"); + } + b = p->binding_handle; + transport = dcerpc_binding_get_transport(p->binding); + + if (transport != NCACN_NP && transport != NCALRPC) { + torture_comment(torture, + "torture_rpc_lsa_lookup is only available " + "over NCACN_NP or NCALRPC"); + return true; + } + + ret &= open_policy(torture, b, &handle); + if (!ret) return false; + + ret &= get_domainsid(torture, b, handle, &dom_sid); + if (!ret) return false; + + ret &= get_downleveltrust(torture, b, handle, &trusted_sid); + if (!ret) return false; + + torture_comment(torture, "domain sid: %s\n", + dom_sid_string(torture, dom_sid)); + + sids[0] = dom_sid_parse_talloc(torture, "S-1-1-0"); + sids[1] = dom_sid_parse_talloc(torture, "S-1-5-4"); + sids[2] = dom_sid_parse_talloc(torture, "S-1-5-32"); + sids[3] = dom_sid_parse_talloc(torture, "S-1-5-32-545"); + sids[4] = dom_sid_dup(torture, dom_sid); + sids[5] = dom_sid_add_rid(torture, dom_sid, 512); + sids[6] = dom_sid_dup(torture, trusted_sid); + sids[7] = dom_sid_add_rid(torture, trusted_sid, 512); + + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 0, + NT_STATUS_INVALID_PARAMETER, NULL); + + { + enum lsa_SidType types[NUM_SIDS] = + { SID_NAME_WKN_GRP, SID_NAME_WKN_GRP, SID_NAME_DOMAIN, + SID_NAME_ALIAS, SID_NAME_DOMAIN, SID_NAME_DOM_GRP, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP }; + + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 1, + NT_STATUS_OK, types); + } + + { + enum lsa_SidType types[NUM_SIDS] = + { SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP }; + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 2, + STATUS_SOME_UNMAPPED, types); + } + + { + enum lsa_SidType types[NUM_SIDS] = + { SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN }; + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 3, + STATUS_SOME_UNMAPPED, types); + } + + { + enum lsa_SidType types[NUM_SIDS] = + { SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN }; + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 4, + STATUS_SOME_UNMAPPED, types); + } + + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 5, + NT_STATUS_NONE_MAPPED, NULL); + + { + enum lsa_SidType types[NUM_SIDS] = + { SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN, + SID_NAME_DOMAIN, SID_NAME_DOM_GRP, + SID_NAME_UNKNOWN, SID_NAME_UNKNOWN }; + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 6, + STATUS_SOME_UNMAPPED, types); + } + + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 7, + NT_STATUS_INVALID_PARAMETER, NULL); + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 8, + NT_STATUS_INVALID_PARAMETER, NULL); + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 9, + NT_STATUS_INVALID_PARAMETER, NULL); + ret &= test_lookupsids(torture, b, handle, sids, NUM_SIDS, 10, + NT_STATUS_INVALID_PARAMETER, NULL); + + return ret; +} + +static bool test_LookupSidsReply(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct policy_handle *handle = NULL; + + struct dom_sid **sids = NULL; + uint32_t num_sids = 1; + + struct lsa_LookupSids r; + struct lsa_SidArray sidarray; + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransNameArray names; + uint32_t count = 0; + + uint32_t i; + const char *dom_sid = "S-1-5-21-1111111111-2222222222-3333333333"; + const char *dom_admin_sid; + struct dcerpc_binding_handle *b = p->binding_handle; + enum dcerpc_transport_t transport = dcerpc_binding_get_transport(p->binding); + + ZERO_STRUCT(r); + ZERO_STRUCT(sidarray); + ZERO_STRUCT(names); + + if (transport != NCACN_NP && transport != NCALRPC) { + torture_comment(tctx, + "test_LookupSidsReply is only available " + "over NCACN_NP or NCALRPC"); + return true; + } + + if (!open_policy(tctx, b, &handle)) { + return false; + } + + dom_admin_sid = talloc_asprintf(tctx, "%s-%d", dom_sid, 512); + + sids = talloc_zero_array(tctx, struct dom_sid *, num_sids); + + sids[0] = dom_sid_parse_talloc(tctx, dom_admin_sid); + + names.count = 0; + names.names = NULL; + + sidarray.num_sids = num_sids; + sidarray.sids = talloc_zero_array(tctx, struct lsa_SidPtr, num_sids); + + for (i=0; i<num_sids; i++) { + sidarray.sids[i].sid = sids[i]; + } + + r.in.handle = handle; + r.in.sids = &sidarray; + r.in.names = &names; + r.in.level = LSA_LOOKUP_NAMES_ALL; + r.in.count = &count; + r.out.names = &names; + r.out.count = &count; + r.out.domains = &domains; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_LookupSids_r(b, tctx, &r), + "LookupSids failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NONE_MAPPED, + "unexpected error code"); + + torture_assert_int_equal(tctx, names.count, num_sids, + "unexpected names count"); + torture_assert(tctx, names.names, + "unexpected names pointer"); + torture_assert_str_equal(tctx, names.names[0].name.string, dom_admin_sid, + "unexpected names[0].string"); + +#if 0 + /* vista sp1 passes, w2k3 sp2 fails */ + torture_assert_int_equal(tctx, domains->count, num_sids, + "unexpected domains count"); + torture_assert(tctx, domains->domains, + "unexpected domains pointer"); + torture_assert_str_equal(tctx, dom_sid_string(tctx, domains->domains[0].sid), dom_sid, + "unexpected domain sid"); +#endif + + return true; +} + +/* check for lookup sids results */ +struct torture_suite *torture_rpc_lsa_lookup_sids(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + + suite = torture_suite_create(mem_ctx, "lsa.lookupsids"); + tcase = torture_suite_add_rpc_iface_tcase(suite, "lsa", + &ndr_table_lsarpc); + + torture_rpc_tcase_add_test(tcase, "LookupSidsReply", test_LookupSidsReply); + + return suite; +} diff --git a/source4/torture/rpc/mdssvc.c b/source4/torture/rpc/mdssvc.c new file mode 100644 index 0000000..afe7068 --- /dev/null +++ b/source4/torture/rpc/mdssvc.c @@ -0,0 +1,1040 @@ +/* + Unix SMB/CIFS implementation. + test suite for the mdssvc RPC serice + + Copyright (C) Ralph Boehme 2019 + + 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_mdssvc_c.h" +#include "param/param.h" +#include "lib/cmdline/cmdline.h" +#include "rpc_server/mdssvc/dalloc.h" +#include "rpc_server/mdssvc/marshalling.h" + +struct torture_mdsscv_state { + struct dcerpc_pipe *p; + struct policy_handle ph; + + /* Known fields used across multiple commands */ + uint32_t dev; + uint32_t flags; + + /* cmd specific or unknown fields */ + struct { + const char share_path[1025]; + uint32_t unkn2; + uint32_t unkn3; + } mdscmd_open; + struct { + uint32_t status; + uint32_t unkn7; + } mdscmd_unknown1; + struct { + uint32_t fragment; + uint32_t unkn9; + } mdscmd_cmd; + struct { + uint32_t status; + } mdscmd_close; +}; + +static bool torture_rpc_mdssvc_setup(struct torture_context *tctx, + void **data) +{ + struct torture_mdsscv_state *state = NULL; + NTSTATUS status; + + state = talloc_zero(tctx, struct torture_mdsscv_state); + if (state == NULL) { + return false; + } + *data = state; + + status = torture_rpc_connection(tctx, &state->p, &ndr_table_mdssvc); + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + return true; +} + +static bool torture_rpc_mdssvc_teardown(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + + TALLOC_FREE(state->p); + TALLOC_FREE(state); + return true; +} + +static bool torture_rpc_mdssvc_open(struct torture_context *tctx, + void **data) +{ + struct torture_mdsscv_state *state = NULL; + struct dcerpc_binding_handle *b = NULL; + const char *share_name = NULL; + const char *share_mount_path = NULL; + NTSTATUS status; + bool ok = true; + + state = talloc_zero(tctx, struct torture_mdsscv_state); + if (state == NULL) { + return false; + } + *data = state; + + status = torture_rpc_connection(tctx, &state->p, &ndr_table_mdssvc); + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + b = state->p->binding_handle; + + share_name = torture_setting_string( + tctx, "spotlight_share", "spotlight"); + share_mount_path = torture_setting_string( + tctx, "share_mount_path", "/foo/bar"); + + state->dev = generate_random(); + state->mdscmd_open.unkn2 = 23; + state->mdscmd_open.unkn3 = 0; + + ZERO_STRUCT(state->ph); + + status = dcerpc_mdssvc_open(b, + state, + &state->dev, + &state->mdscmd_open.unkn2, + &state->mdscmd_open.unkn3, + share_mount_path, + share_name, + state->mdscmd_open.share_path, + &state->ph); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_open failed\n"); + + status = dcerpc_mdssvc_unknown1(b, + state, + &state->ph, + 0, + state->dev, + state->mdscmd_open.unkn2, + 0, + geteuid(), + getegid(), + &state->mdscmd_unknown1.status, + &state->flags, + &state->mdscmd_unknown1.unkn7); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + +done: + if (!ok) { + (void)dcerpc_mdssvc_close(b, + state, + &state->ph, + 0, + state->dev, + state->mdscmd_open.unkn2, + 0, + &state->ph, + &state->mdscmd_close.status); + ZERO_STRUCT(state); + } + return ok; +} + +static bool torture_rpc_mdssvc_close(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + NTSTATUS status; + bool ok = true; + + torture_comment(tctx, "test_teardown_mdssvc_disconnect\n"); + + status = dcerpc_mdssvc_close(b, + state, + &state->ph, + 0, + state->dev, + state->mdscmd_open.unkn2, + 0, + &state->ph, + &state->mdscmd_close.status); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_close failed\n"); + + ZERO_STRUCT(state); + +done: + return ok; +} + +/* + * Test unknown share name + */ +static bool test_mdssvc_open_unknown_share(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + struct policy_handle nullh; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn3; + uint32_t device_id_out; + uint32_t unkn2_out; + uint32_t unkn3_out; + const char *share_mount_path = NULL; + const char *share_name = NULL; + const char share_path[1025] = "X"; + NTSTATUS status; + bool ok = true; + + share_name = torture_setting_string( + tctx, "unknown_share", "choukawoohoo"); + share_mount_path = torture_setting_string( + tctx, "share_mount_path", "/foo/bar"); + + device_id_out = device_id = generate_random(); + unkn2_out = unkn2 = generate_random(); + unkn3_out = unkn3 = generate_random(); + + ZERO_STRUCT(ph); + ZERO_STRUCT(nullh); + + status = dcerpc_mdssvc_open(b, + tctx, + &device_id_out, + &unkn2_out, + &unkn3_out, + share_mount_path, + share_name, + share_path, + &ph); + + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_open failed\n"); + + torture_assert_u32_equal_goto(tctx, device_id_out, device_id, ok, done, + "Bad device_id\n"); + + torture_assert_u32_equal_goto(tctx, unkn2_out, unkn2, ok, done, + "Bad unkn2\n"); + + torture_assert_u32_equal_goto(tctx, unkn3_out, unkn3, ok, done, + "Bad unkn3\n"); + + torture_assert_goto(tctx, share_path[0] == '\0', ok, done, + "Expected empty string as share path\n"); + + torture_assert_mem_equal_goto(tctx, &ph, &nullh, + sizeof(ph), ok, done, + "Expected all-zero policy handle\n"); + +done: + return ok; +} + +/* + * Test on a share where Spotlight is not enabled + */ +static bool test_mdssvc_open_spotlight_disabled(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + struct policy_handle nullh; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn3; + uint32_t device_id_out; + uint32_t unkn2_out; + uint32_t unkn3_out; + const char *share_mount_path = NULL; + const char *share_name = NULL; + const char share_path[1025] = ""; + NTSTATUS status; + bool ok = true; + + share_name = torture_setting_string( + tctx, "no_spotlight_share", "no_spotlight"); + share_mount_path = torture_setting_string( + tctx, "share_mount_path", "/foo/bar"); + + device_id_out = device_id = generate_random(); + unkn2_out = unkn2 = 23; + unkn3_out = unkn3 = 0; + + ZERO_STRUCT(ph); + ZERO_STRUCT(nullh); + + status = dcerpc_mdssvc_open(b, + tctx, + &device_id_out, + &unkn2_out, + &unkn3_out, + share_mount_path, + share_name, + share_path, + &ph); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_open failed\n"); + + torture_assert_u32_equal_goto(tctx, device_id, device_id_out, ok, done, + "Bad device_id\n"); + + torture_assert_u32_equal_goto(tctx, unkn2, unkn2_out, + ok, done, "Bad unkn2\n"); + + torture_assert_u32_equal_goto(tctx, unkn3, unkn3_out, + ok, done, "Bad unkn3\n"); + + torture_assert_goto(tctx, share_path[0] == '\0', ok, done, + "Expected empty string as share path\n"); + + torture_assert_mem_equal_goto(tctx, &ph, &nullh, + sizeof(ph), ok, done, + "Expected all-zero policy handle\n"); + +done: + return ok; +} + +static bool test_mdssvc_close(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + struct policy_handle close_ph; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn3; + const char *share_mount_path = NULL; + const char *share_name = NULL; + const char share_path[1025] = ""; + uint32_t close_status; + DATA_BLOB ph_blob; + DATA_BLOB close_ph_blob; + NTSTATUS status; + bool ok = true; + + share_name = torture_setting_string( + tctx, "spotlight_share", "spotlight"); + share_mount_path = torture_setting_string( + tctx, "share_mount_path", "/foo/bar"); + + device_id = generate_random(); + unkn2 = 23; + unkn3 = 0; + + ZERO_STRUCT(ph); + ZERO_STRUCT(close_ph); + + status = dcerpc_mdssvc_open(b, + tctx, + &device_id, + &unkn2, + &unkn3, + share_mount_path, + share_name, + share_path, + &ph); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_open failed\n"); + + status = dcerpc_mdssvc_close(b, + tctx, + &ph, + 0, + device_id, + unkn2, + 0, + &close_ph, + &close_status); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_open failed\n"); + + ph_blob = (DATA_BLOB) { + .data = (uint8_t *)&ph, + .length = sizeof(struct policy_handle) + }; + close_ph_blob = (DATA_BLOB) { + .data = (uint8_t *)&close_ph, + .length = sizeof(struct policy_handle), + }; + + torture_assert_data_blob_equal(tctx, close_ph_blob, ph_blob, + "bad blob"); + + torture_comment(tctx, "Test close with a all-zero handle\n"); + + ZERO_STRUCT(ph); + status = dcerpc_mdssvc_close(b, + tctx, + &ph, + 0, + device_id, + unkn2, + 0, + &close_ph, + &close_status); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_close failed\n"); + + torture_assert_data_blob_equal(tctx, close_ph_blob, ph_blob, + "bad blob"); + +done: + return ok; +} + +static bool test_mdssvc_null_ph(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle nullh; + struct policy_handle ph; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn7; + uint32_t cmd_status; + uint32_t flags; + NTSTATUS status; + bool ok = true; + + device_id = generate_random(); + unkn2 = 23; + unkn7 = 0; + cmd_status = 0; + + ZERO_STRUCT(nullh); + ZERO_STRUCT(ph); + + status = dcerpc_mdssvc_unknown1(b, + tctx, + &ph, + 0, + device_id, + unkn2, + 0, + geteuid(), + getegid(), + &cmd_status, + &flags, + &unkn7); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + + torture_assert_mem_equal_goto(tctx, &ph, &nullh, + sizeof(ph), ok, done, + "Expected all-zero policy handle\n"); + +done: + return ok; +} + +static bool test_mdssvc_invalid_ph_unknown1(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn7; + uint32_t cmd_status; + uint32_t flags; + NTSTATUS status; + bool ok = true; + + device_id = generate_random(); + unkn2 = 23; + unkn7 = 0; + cmd_status = 0; + + ZERO_STRUCT(ph); + ph.uuid = GUID_random(); + + status = dcerpc_mdssvc_unknown1(b, + tctx, + &ph, + 0, + device_id, + unkn2, + 0, + geteuid(), + getegid(), + &cmd_status, + &flags, + &unkn7); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_RPC_PROTOCOL_ERROR, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + +done: + return ok; +} + +static bool test_mdssvc_invalid_ph_cmd(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + struct mdssvc_blob request_blob; + struct mdssvc_blob response_blob; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn9; + uint32_t fragment; + uint32_t flags; + NTSTATUS status; + bool ok = true; + + device_id = generate_random(); + unkn2 = 23; + unkn9 = 0; + fragment = 0; + flags = UINT32_C(0x6b000001); + + ZERO_STRUCT(ph); + ph.uuid = GUID_random(); + + request_blob.spotlight_blob = talloc_array(state, + uint8_t, + 0); + torture_assert_not_null_goto(tctx, request_blob.spotlight_blob, + ok, done, "dalloc_zero failed\n"); + request_blob.size = 0; + request_blob.length = 0; + request_blob.size = 0; + + status = dcerpc_mdssvc_cmd(b, + state, + &ph, + 0, + device_id, + unkn2, + 0, + flags, + request_blob, + 0, + 64 * 1024, + 1, + 64 * 1024, + 0, + 0, + &fragment, + &response_blob, + &unkn9); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_RPC_PROTOCOL_ERROR, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + +done: + return ok; +} + +static uint8_t test_sl_unpack_loop_buf[] = { + 0x34, 0x33, 0x32, 0x31, 0x33, 0x30, 0x64, 0x6d, + 0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x07, 0x04, 0x00, 0x00, 0x00, + 0x66, 0x65, 0x74, 0x63, 0x68, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x3a, + 0x66, 0x6f, 0x72, 0x4f, 0x49, 0x44, 0x41, 0x72, + 0x72, 0x61, 0x79, 0x3a, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x3a, 0x00, 0x00, 0x00, 0xea, + 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x00, + 0x0a, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x00, + 0x6b, 0x4d, 0x44, 0x49, 0x74, 0x65, 0x6d, 0x50, + 0x61, 0x74, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x87, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0xdd, 0x0a, 0x20, 0x00, 0x00, 0x6b, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static bool test_mdssvc_sl_unpack_loop(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct mdssvc_blob request_blob; + struct mdssvc_blob response_blob; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn9; + uint32_t fragment; + uint32_t flags; + NTSTATUS status; + bool ok = true; + + device_id = UINT32_C(0x2f000045); + unkn2 = 23; + unkn9 = 0; + fragment = 0; + flags = UINT32_C(0x6b000001); + + request_blob.spotlight_blob = test_sl_unpack_loop_buf; + request_blob.size = sizeof(test_sl_unpack_loop_buf); + request_blob.length = sizeof(test_sl_unpack_loop_buf); + + status = dcerpc_mdssvc_cmd(b, + state, + &state->ph, + 0, + device_id, + unkn2, + 0, + flags, + request_blob, + 0, + 64 * 1024, + 1, + 64 * 1024, + 0, + 0, + &fragment, + &response_blob, + &unkn9); + torture_assert_ntstatus_ok_goto( + tctx, status, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + +done: + return ok; +} + +static bool test_sl_dict_type_safety(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct mdssvc_blob request_blob; + struct mdssvc_blob response_blob; + uint64_t ctx1 = 0xdeadbeef; + uint64_t ctx2 = 0xcafebabe; + uint32_t device_id; + uint32_t unkn2; + uint32_t unkn9; + uint32_t fragment; + uint32_t flags; + DALLOC_CTX *d = NULL; + sl_array_t *array1 = NULL, *array2 = NULL; + sl_dict_t *arg = NULL; + int result; + NTSTATUS status; + bool ok = true; + + device_id = UINT32_C(0x2f000045); + unkn2 = 23; + unkn9 = 0; + fragment = 0; + flags = UINT32_C(0x6b000001); + + d = dalloc_new(tctx); + torture_assert_not_null_goto(tctx, d, + ok, done, "dalloc_new failed\n"); + + array1 = dalloc_zero(d, sl_array_t); + torture_assert_not_null_goto(tctx, array1, + ok, done, "dalloc_zero failed\n"); + + array2 = dalloc_zero(d, sl_array_t); + torture_assert_not_null_goto(tctx, array2, + ok, done, "dalloc_new failed\n"); + + result = dalloc_stradd(array2, "openQueryWithParams:forContext:"); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_add_copy(array2, &ctx1, uint64_t); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_add_copy(array2, &ctx2, uint64_t); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + arg = dalloc_zero(array1, sl_dict_t); + torture_assert_not_null_goto(tctx, d, + ok, done, "dalloc_zero failed\n"); + + result = dalloc_stradd(arg, "kMDQueryString"); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_stradd(arg, "*"); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_stradd(arg, "kMDScopeArray"); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_stradd(arg, "AAAABBBB"); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_stradd failed\n"); + + result = dalloc_add(array1, array2, sl_array_t); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_add failed\n"); + + result = dalloc_add(array1, arg, sl_dict_t); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_add failed\n"); + + result = dalloc_add(d, array1, sl_array_t); + torture_assert_goto(tctx, result == 0, + ok, done, "dalloc_add failed\n"); + + torture_comment(tctx, "%s", dalloc_dump(d, 0)); + + request_blob.spotlight_blob = talloc_array(tctx, + uint8_t, + 64 * 1024); + torture_assert_not_null_goto(tctx, request_blob.spotlight_blob, + ok, done, "dalloc_new failed\n"); + request_blob.size = 64 * 1024; + + status = sl_pack_alloc(tctx, d, &request_blob, 64 * 1024); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "sl_pack_alloc() failed\n"); + + status = dcerpc_mdssvc_cmd(b, + state, + &state->ph, + 0, + device_id, + unkn2, + 0, + flags, + request_blob, + 0, + 64 * 1024, + 1, + 64 * 1024, + 0, + 0, + &fragment, + &response_blob, + &unkn9); + torture_assert_ntstatus_ok_goto( + tctx, status, ok, done, + "dcerpc_mdssvc_cmd failed\n"); + +done: + return ok; +} + +static bool test_mdssvc_invalid_ph_close(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + struct policy_handle ph; + uint32_t device_id; + uint32_t unkn2; + uint32_t close_status; + NTSTATUS status; + bool ok = true; + + device_id = generate_random(); + unkn2 = 23; + close_status = 0; + + ZERO_STRUCT(ph); + ph.uuid = GUID_random(); + + status = dcerpc_mdssvc_close(b, + state, + &ph, + 0, + device_id, + unkn2, + 0, + &ph, + &close_status); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_RPC_PROTOCOL_ERROR, ok, done, + "dcerpc_mdssvc_unknown1 failed\n"); + +done: + return ok; +} + +/* + * Test fetchAttributes with unknown CNID + */ +static bool test_mdssvc_fetch_attr_unknown_cnid(struct torture_context *tctx, + void *data) +{ + struct torture_mdsscv_state *state = talloc_get_type_abort( + data, struct torture_mdsscv_state); + struct dcerpc_binding_handle *b = state->p->binding_handle; + uint32_t max_fragment_size = 64 * 1024; + struct mdssvc_blob request_blob; + struct mdssvc_blob response_blob; + DALLOC_CTX *d = NULL, *mds_reply = NULL; + uint64_t *uint64var = NULL; + sl_array_t *array = NULL; + sl_array_t *cmd_array = NULL; + sl_array_t *attr_array = NULL; + sl_cnids_t *cnids = NULL; + void *path = NULL; + const char *path_type = NULL; + uint64_t ino64; + NTSTATUS status; + int ret; + bool ok = true; + + d = dalloc_new(state); + torture_assert_not_null_goto(tctx, d, ret, done, "dalloc_new failed\n"); + + array = dalloc_zero(d, sl_array_t); + torture_assert_not_null_goto(tctx, array, ret, done, + "dalloc_zero failed\n"); + + ret = dalloc_add(d, array, sl_array_t); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_add failed\n"); + + cmd_array = dalloc_zero(d, sl_array_t); + torture_assert_not_null_goto(tctx, cmd_array, ret, done, + "dalloc_zero failed\n"); + + ret = dalloc_add(array, cmd_array, sl_array_t); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_add failed\n"); + + ret = dalloc_stradd(cmd_array, "fetchAttributes:forOIDArray:context:"); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_stradd failed\n"); + + uint64var = talloc_zero_array(cmd_array, uint64_t, 2); + torture_assert_not_null_goto(tctx, uint64var, ret, done, + "talloc_zero_array failed\n"); + talloc_set_name(uint64var, "uint64_t *"); + + uint64var[0] = 0x500a; + uint64var[1] = 0; + + ret = dalloc_add(cmd_array, &uint64var[0], uint64_t *); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_add failed\n"); + + attr_array = dalloc_zero(d, sl_array_t); + torture_assert_not_null_goto(tctx, attr_array, ret, done, + "dalloc_zero failed\n"); + + ret = dalloc_add(array, attr_array, sl_array_t); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_add failed\n"); + + ret = dalloc_stradd(attr_array, "kMDItemPath"); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_stradd failed\n"); + + /* CNIDs */ + cnids = talloc_zero(array, sl_cnids_t); + torture_assert_not_null_goto(tctx, cnids, ret, done, + "talloc_zero failed\n"); + + cnids->ca_cnids = dalloc_new(cnids); + torture_assert_not_null_goto(tctx, cnids->ca_cnids, ret, done, + "dalloc_new failed\n"); + + cnids->ca_unkn1 = 0xadd; + cnids->ca_context = 0x6b000020; + + ino64 = UINT64_C(64382947389618974); + ret = dalloc_add_copy(cnids->ca_cnids, &ino64, uint64_t); + torture_assert_goto(tctx, ret == 0, ret, done, + "dalloc_add_copy failed\n"); + + ret = dalloc_add(array, cnids, sl_cnids_t); + torture_assert_goto(tctx, ret == 0, ret, done, "dalloc_add failed\n"); + + status = sl_pack_alloc(tctx, d, &request_blob, max_fragment_size); + torture_assert_ntstatus_ok_goto(tctx, status, ok, done, + "sl_pack_alloc() failed\n"); + + status = dcerpc_mdssvc_cmd(b, + state, + &state->ph, + 0, + state->dev, + state->mdscmd_open.unkn2, + 0, + state->flags, + request_blob, + 0, + max_fragment_size, + 1, + max_fragment_size, + 0, + 0, + &state->mdscmd_cmd.fragment, + &response_blob, + &state->mdscmd_cmd.unkn9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "dcerpc_mdssvc_cmd failed\n"); + + mds_reply = dalloc_new(state); + torture_assert_not_null_goto(tctx, mds_reply, ret, done, + "dalloc_zero failed\n"); + + ok = sl_unpack(mds_reply, + (char *)response_blob.spotlight_blob, + response_blob.length); + torture_assert_goto(tctx, ok, ret, done, "dalloc_add failed\n"); + + torture_comment(tctx, "%s", dalloc_dump(mds_reply, 0)); + + path = dalloc_get(mds_reply, + "DALLOC_CTX", 0, + "DALLOC_CTX", 2, + "DALLOC_CTX", 0, + "sl_nil_t", 1); + torture_assert_not_null_goto(tctx, path, ret, done, + "dalloc_get path failed\n"); + + path_type = talloc_get_name(path); + + torture_assert_str_equal_goto(tctx, path_type, "sl_nil_t", ret, done, + "Wrong dalloc object type\n"); + +done: + return ok; +} + +struct torture_suite *torture_rpc_mdssvc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create( + mem_ctx, "mdssvc"); + struct torture_tcase *tcase = NULL; + + tcase = torture_suite_add_tcase(suite, "rpccmd"); + if (tcase == NULL) { + return NULL; + } + torture_tcase_set_fixture(tcase, + torture_rpc_mdssvc_setup, + torture_rpc_mdssvc_teardown); + + torture_tcase_add_simple_test(tcase, + "open_unknown_share", + test_mdssvc_open_unknown_share); + + torture_tcase_add_simple_test(tcase, + "open_spotlight_disabled", + test_mdssvc_open_spotlight_disabled); + + torture_tcase_add_simple_test(tcase, + "close", + test_mdssvc_close); + + torture_tcase_add_simple_test(tcase, + "null_ph", + test_mdssvc_null_ph); + + tcase = torture_suite_add_tcase(suite, "disconnect1"); + if (tcase == NULL) { + return NULL; + } + torture_tcase_set_fixture(tcase, + torture_rpc_mdssvc_open, + torture_rpc_mdssvc_close); + + torture_tcase_add_simple_test(tcase, + "invalid_ph_unknown1", + test_mdssvc_invalid_ph_unknown1); + + tcase = torture_suite_add_tcase(suite, "disconnect2"); + if (tcase == NULL) { + return NULL; + } + torture_tcase_set_fixture(tcase, + torture_rpc_mdssvc_open, + torture_rpc_mdssvc_close); + + torture_tcase_add_simple_test(tcase, + "invalid_ph_cmd", + test_mdssvc_invalid_ph_cmd); + + tcase = torture_suite_add_tcase(suite, "disconnect3"); + if (tcase == NULL) { + return NULL; + } + torture_tcase_set_fixture(tcase, + torture_rpc_mdssvc_open, + torture_rpc_mdssvc_close); + + torture_tcase_add_simple_test(tcase, + "invalid_ph_close", + test_mdssvc_invalid_ph_close); + + tcase = torture_suite_add_tcase(suite, "mdscmd"); + if (tcase == NULL) { + return NULL; + } + torture_tcase_set_fixture(tcase, + torture_rpc_mdssvc_open, + torture_rpc_mdssvc_close); + + torture_tcase_add_simple_test(tcase, + "fetch_unknown_cnid", + test_mdssvc_fetch_attr_unknown_cnid); + + torture_tcase_add_simple_test(tcase, + "mdssvc_sl_unpack_loop", + test_mdssvc_sl_unpack_loop); + + torture_tcase_add_simple_test(tcase, + "sl_dict_type_safety", + test_sl_dict_type_safety); + + return suite; +} diff --git a/source4/torture/rpc/mgmt.c b/source4/torture/rpc/mgmt.c new file mode 100644 index 0000000..f3344c9 --- /dev/null +++ b/source4/torture/rpc/mgmt.c @@ -0,0 +1,322 @@ +/* + Unix SMB/CIFS implementation. + test suite for mgmt rpc operations + + Copyright (C) Andrew Tridgell 2003 + + 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_mgmt_c.h" +#include "auth/gensec/gensec.h" +#include "librpc/ndr/ndr_table.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + + +/* + ask the server what interface IDs are available on this endpoint +*/ +bool test_inq_if_ids(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + bool (*per_id_test)(struct torture_context *, + const struct ndr_interface_table *iface, + TALLOC_CTX *mem_ctx, + struct ndr_syntax_id *id), + const void *priv) +{ + struct mgmt_inq_if_ids r; + struct rpc_if_id_vector_t *vector; + int i; + + vector = talloc(mem_ctx, struct rpc_if_id_vector_t); + r.out.if_id_vector = &vector; + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_inq_if_ids_r(b, mem_ctx, &r), + "inq_if_ids failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "inq_if_ids gave unexpected error code"); + + if (!vector) { + torture_comment(tctx, "inq_if_ids gave NULL if_id_vector\n"); + return false; + } + + for (i=0;i<vector->count;i++) { + struct ndr_syntax_id *id = vector->if_id[i].id; + if (!id) continue; + + torture_comment(tctx, "\tuuid %s version 0x%08x '%s'\n", + GUID_string(mem_ctx, &id->uuid), + id->if_version, + ndr_interface_name(&id->uuid, id->if_version)); + + if (per_id_test) { + per_id_test(tctx, priv, mem_ctx, id); + } + } + + return true; +} + +static bool test_inq_stats(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx) +{ + struct mgmt_inq_stats r; + struct mgmt_statistics statistics; + + r.in.max_count = MGMT_STATS_ARRAY_MAX_SIZE; + r.in.unknown = 0; + r.out.statistics = &statistics; + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_inq_stats_r(b, mem_ctx, &r), + "inq_stats failed"); + + if (statistics.count != MGMT_STATS_ARRAY_MAX_SIZE) { + torture_comment(tctx, "Unexpected array size %d\n", statistics.count); + return false; + } + + torture_comment(tctx, "\tcalls_in %6d calls_out %6d\n\tpkts_in %6d pkts_out %6d\n", + statistics.statistics[MGMT_STATS_CALLS_IN], + statistics.statistics[MGMT_STATS_CALLS_OUT], + statistics.statistics[MGMT_STATS_PKTS_IN], + statistics.statistics[MGMT_STATS_PKTS_OUT]); + + return true; +} + +static bool test_inq_princ_name_size(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t authn_proto, + const char *expected_princ_name) +{ + struct mgmt_inq_princ_name r; + uint32_t len, i; + + len = strlen(expected_princ_name); + + r.in.authn_proto = authn_proto; + + /* + * 0 gives NT_STATUS_RPC_BAD_STUB_DATA + */ + + for (i=1; i <= len; i++) { + r.in.princ_name_size = i; + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_inq_princ_name_r(b, tctx, &r), + "mgmt_inq_princ_name failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INSUFFICIENT_BUFFER, + "mgmt_inq_princ_name failed"); + } + + r.in.princ_name_size = len + 1; + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_inq_princ_name_r(b, tctx, &r), + "mgmt_inq_princ_name failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "mgmt_inq_princ_name failed"); + + return true; +} + +static bool test_inq_princ_name(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + struct mgmt_inq_princ_name r; + int i; + bool ret = false; + + for (i=0;i<256;i++) { + r.in.authn_proto = i; /* DCERPC_AUTH_TYPE_* */ + r.in.princ_name_size = 100; + + status = dcerpc_mgmt_inq_princ_name_r(b, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + continue; + } + if (W_ERROR_IS_OK(r.out.result)) { + const char *name = gensec_get_name_by_authtype(NULL, i); + ret = true; + if (name) { + torture_comment(tctx, "\tprinciple name for proto %u (%s) is '%s'\n", + i, name, r.out.princ_name); + } else { + torture_comment(tctx, "\tprinciple name for proto %u is '%s'\n", + i, r.out.princ_name); + } + + switch (i) { + case DCERPC_AUTH_TYPE_KRB5: + case DCERPC_AUTH_TYPE_NTLMSSP: + case DCERPC_AUTH_TYPE_SPNEGO: + torture_assert(tctx, + test_inq_princ_name_size(tctx, b, i, r.out.princ_name), + "failed"); + break; + case DCERPC_AUTH_TYPE_SCHANNEL: + /* + * for some reason schannel behaves differently + * + */ + default: + break; + } + } + } + + if (!ret) { + torture_comment(tctx, "\tno principle names?\n"); + } + + return true; +} + +static bool test_is_server_listening(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx) +{ + struct mgmt_is_server_listening r; + r.out.status = talloc(mem_ctx, uint32_t); + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_is_server_listening_r(b, mem_ctx, &r), + "is_server_listening failed"); + + if (*r.out.status != 0 || r.out.result == 0) { + torture_comment(tctx, "\tserver is NOT listening\n"); + } else { + torture_comment(tctx, "\tserver is listening\n"); + } + + return true; +} + +static bool test_stop_server_listening(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx) +{ + struct mgmt_stop_server_listening r; + + torture_assert_ntstatus_ok(tctx, + dcerpc_mgmt_stop_server_listening_r(b, mem_ctx, &r), + "stop_server_listening failed"); + + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "\tserver refused to stop listening - %s\n", win_errstr(r.out.result)); + } else { + torture_comment(tctx, "\tserver allowed a stop_server_listening request\n"); + return false; + } + + return true; +} + + +bool torture_rpc_mgmt(struct torture_context *tctx) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + TALLOC_CTX *mem_ctx, *loop_ctx; + bool ret = true; + const struct ndr_interface_list *l; + struct dcerpc_binding *b; + + mem_ctx = talloc_init("torture_rpc_mgmt"); + + status = torture_rpc_binding(tctx, &b); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return false; + } + + for (l=ndr_table_list();l;l=l->next) { + struct dcerpc_binding_handle *bh; + + loop_ctx = talloc_named(mem_ctx, 0, "torture_rpc_mgmt loop context"); + + /* some interfaces are not mappable */ + if (l->table->num_calls == 0 || + strcmp(l->table->name, "mgmt") == 0) { + talloc_free(loop_ctx); + continue; + } + + torture_comment(tctx, "\nTesting pipe '%s'\n", l->table->name); + + status = dcerpc_epm_map_binding(loop_ctx, b, l->table, + tctx->ev, tctx->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to map port for uuid %s\n", + GUID_string(loop_ctx, &l->table->syntax_id.uuid)); + talloc_free(loop_ctx); + continue; + } + + lpcfg_set_cmdline(tctx->lp_ctx, "torture:binding", dcerpc_binding_string(loop_ctx, b)); + + status = torture_rpc_connection(tctx, &p, &ndr_table_mgmt); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + torture_comment(tctx, "Interface not available - skipping\n"); + talloc_free(loop_ctx); + continue; + } + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(loop_ctx); + torture_comment(tctx, "Interface not available (%s) - skipping\n", nt_errstr(status)); + ret = false; + continue; + } + bh = p->binding_handle; + + if (!test_is_server_listening(tctx, bh, loop_ctx)) { + ret = false; + } + + if (!test_stop_server_listening(tctx, bh, loop_ctx)) { + ret = false; + } + + if (!test_inq_stats(tctx, bh, loop_ctx)) { + ret = false; + } + + if (!test_inq_princ_name(tctx, bh, loop_ctx)) { + ret = false; + } + + if (!test_inq_if_ids(tctx, bh, loop_ctx, NULL, NULL)) { + ret = false; + } + + } + + return ret; +} diff --git a/source4/torture/rpc/netlogon.c b/source4/torture/rpc/netlogon.c new file mode 100644 index 0000000..a3d190f --- /dev/null +++ b/source4/torture/rpc/netlogon.c @@ -0,0 +1,6038 @@ +/* + Unix SMB/CIFS implementation. + + test suite for netlogon rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004 + Copyright (C) Tim Potter 2003 + 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 "lib/events/events.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "../lib/crypto/crypto.h" +#include "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "param/param.h" +#include "libcli/security/security.h" +#include <ldb.h> +#include "lib/util/util_ldb.h" +#include "ldb_wrap.h" +#include "lib/replace/system/network.h" +#include "dsdb/samdb/samdb.h" + +#undef strcasecmp + +#define TEST_MACHINE_NAME "torturetest" + +static bool test_netr_broken_binding_handle(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_DsRGetSiteName r; + const char *site = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.computer_name = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + r.out.site = &site; + + torture_comment(tctx, + "Testing netlogon request with correct binding handle: %s\n", + r.in.computer_name); + + status = dcerpc_netr_DsRGetSiteName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "Netlogon request with broken binding handle"); + torture_assert_werr_ok(tctx, r.out.result, + "Netlogon request with broken binding handle"); + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, + "Skipping broken binding handle check against Samba"); + } + + r.in.computer_name = talloc_asprintf(tctx, "\\\\\\\\%s", + dcerpc_server_name(p)); + + torture_comment(tctx, + "Testing netlogon request with broken binding handle: %s\n", + r.in.computer_name); + + status = dcerpc_netr_DsRGetSiteName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "Netlogon request with broken binding handle"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_INVALID_COMPUTERNAME, + "Netlogon request with broken binding handle"); + + r.in.computer_name = "\\\\\\\\THIS_IS_NOT_VALID"; + + torture_comment(tctx, + "Testing netlogon request with broken binding handle: %s\n", + r.in.computer_name); + + status = dcerpc_netr_DsRGetSiteName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "Netlogon request with broken binding handle"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_INVALID_COMPUTERNAME, + "Netlogon request with broken binding handle"); + + return true; +} + +static bool test_LogonUasLogon(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_LogonUasLogon r; + struct netr_UasInfo *info = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = NULL; + r.in.account_name = cli_credentials_get_username( + samba_cmdline_get_creds()); + r.in.workstation = TEST_MACHINE_NAME; + r.out.info = &info; + + status = dcerpc_netr_LogonUasLogon_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonUasLogon"); + + return true; +} + +static bool test_LogonUasLogoff(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_LogonUasLogoff r; + struct netr_UasLogoffInfo info; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = NULL; + r.in.account_name = cli_credentials_get_username( + samba_cmdline_get_creds()); + r.in.workstation = TEST_MACHINE_NAME; + r.out.info = &info; + + status = dcerpc_netr_LogonUasLogoff_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonUasLogoff"); + + return true; +} + +bool test_SetupCredentials(struct dcerpc_pipe *p, struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState **creds_out) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + const struct samr_Password *mach_password; + const char *machine_name; + struct dcerpc_binding_handle *b = p->binding_handle; + + mach_password = cli_credentials_get_nt_hash(credentials, tctx); + machine_name = cli_credentials_get_workstation(credentials); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(credentials); + a.in.computer_name = machine_name; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + 0); + torture_assert(tctx, creds != NULL, "memory allocation"); + + + torture_comment(tctx, "Testing ServerAuthenticate\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate_r(b, tctx, &a), + "ServerAuthenticate failed"); + + /* This allows the tests to continue against the more fussy windows 2008 */ + if (NT_STATUS_EQUAL(a.out.result, NT_STATUS_DOWNGRADE_DETECTED)) { + return test_SetupCredentials2(p, tctx, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + credentials, + cli_credentials_get_secure_channel_type(credentials), + creds_out); + } + + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), + "Credential chaining failed"); + + *creds_out = creds; + return true; +} + +bool test_SetupCredentials2ex(struct dcerpc_pipe *p, struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + const char *computer_name, + enum netr_SchannelType sec_chan_type, + NTSTATUS expected_result, + struct netlogon_creds_CredentialState **creds_out) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate2 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + const struct samr_Password *mach_password; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *account_name = cli_credentials_get_username(machine_credentials); + + mach_password = cli_credentials_get_nt_hash(machine_credentials, tctx); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = computer_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = computer_name; + a.in.negotiate_flags = &negotiate_flags; + a.out.negotiate_flags = &negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate2\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal(tctx, a.out.result, expected_result, + "ServerAuthenticate2 unexpected"); + + if (NT_STATUS_IS_OK(expected_result)) { + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), + "Credential chaining failed"); + } else { + torture_assert(tctx, !netlogon_creds_client_check(creds, &credentials3), + "Credential chaining passed unexptected"); + } + + torture_comment(tctx, "negotiate_flags=0x%08x\n", negotiate_flags); + + *creds_out = creds; + return true; +} + +bool test_SetupCredentials2(struct dcerpc_pipe *p, struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + enum netr_SchannelType sec_chan_type, + struct netlogon_creds_CredentialState **creds_out) +{ + const char *computer_name = + cli_credentials_get_workstation(machine_credentials); + + return test_SetupCredentials2ex(p, tctx, negotiate_flags, + machine_credentials, + computer_name, + sec_chan_type, + NT_STATUS_OK, + creds_out); +} + +bool test_SetupCredentials3(struct dcerpc_pipe *p, struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + struct netlogon_creds_CredentialState **creds_out) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b = NULL; + + if (p == NULL) { + return false; + } + + b = p->binding_handle; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &negotiate_flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + torture_comment(tctx, "negotiate_flags=0x%08x\n", negotiate_flags); + + /* Prove that requesting a challenge again won't break it */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + *creds_out = creds; + return true; +} + +bool test_SetupCredentialsDowngrade(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t negotiate_flags = 0; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &negotiate_flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_DOWNGRADE_DETECTED, "ServerAuthenticate3 should have failed"); + + negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 should succeed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + torture_comment(tctx, "negotiate_flags=0x%08x\n", negotiate_flags); + + /* Prove that requesting a challenge again won't break it */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + return true; +} + +bool test_SetupCredentialsPipe(const struct dcerpc_pipe *p1, + struct torture_context *tctx, + struct cli_credentials *machine_credentials, + struct netlogon_creds_CredentialState *creds, + uint32_t additional_flags, + struct dcerpc_pipe **_p2) +{ + NTSTATUS status; + struct dcerpc_binding *b2 = NULL; + struct dcerpc_pipe *p2 = NULL; + + b2 = dcerpc_binding_dup(tctx, p1->binding); + torture_assert(tctx, b2 != NULL, "dcerpc_binding_dup"); + dcerpc_binding_set_flags(b2, + DCERPC_SCHANNEL | additional_flags, + DCERPC_AUTH_OPTIONS); + + cli_credentials_set_netlogon_creds(machine_credentials, creds); + status = dcerpc_pipe_connect_b(tctx, &p2, b2, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx); + cli_credentials_set_netlogon_creds(machine_credentials, NULL); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_pipe_connect_b schannel"); + + *_p2 = p2; + return true; +} + +static bool test_ServerReqChallenge( + struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netr_ServerReqChallenge r; + struct netr_Credential credentials1, credentials2, credentials3; + const char *machine_name; + struct dcerpc_binding_handle *b = p->binding_handle; + struct netr_ServerAuthenticate2 a; + uint32_t in_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + uint32_t out_negotiate_flags = 0; + const struct samr_Password *mach_password = NULL; + enum netr_SchannelType sec_chan_type = 0; + struct netlogon_creds_CredentialState *creds = NULL; + const char *account_name = NULL; + + machine_name = cli_credentials_get_workstation(credentials); + mach_password = cli_credentials_get_nt_hash(credentials, tctx); + account_name = cli_credentials_get_username(credentials); + sec_chan_type = cli_credentials_get_secure_channel_type(credentials); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok( + tctx, + r.out.result, + "ServerReqChallenge failed"); + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = machine_name; + a.in.negotiate_flags = &in_negotiate_flags; + a.out.negotiate_flags = &out_negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + in_negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate2\n"); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal( + tctx, + a.out.result, + NT_STATUS_OK, + "ServerAuthenticate2 unexpected"); + + return true; +} + +static bool test_ServerReqChallenge_zero_challenge( + struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netr_ServerReqChallenge r; + struct netr_Credential credentials1, credentials2, credentials3; + const char *machine_name; + struct dcerpc_binding_handle *b = p->binding_handle; + struct netr_ServerAuthenticate2 a; + uint32_t in_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + uint32_t out_negotiate_flags = 0; + const struct samr_Password *mach_password = NULL; + enum netr_SchannelType sec_chan_type = 0; + struct netlogon_creds_CredentialState *creds = NULL; + const char *account_name = NULL; + + machine_name = cli_credentials_get_workstation(credentials); + mach_password = cli_credentials_get_nt_hash(credentials, tctx); + account_name = cli_credentials_get_username(credentials); + sec_chan_type = cli_credentials_get_secure_channel_type(credentials); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + /* + * Set the client challenge to zero, this should fail + * CVE-2020-1472(ZeroLogon) + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14497 + */ + ZERO_STRUCT(credentials1); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok( + tctx, + r.out.result, + "ServerReqChallenge failed"); + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = machine_name; + a.in.negotiate_flags = &in_negotiate_flags; + a.out.negotiate_flags = &out_negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + in_negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate2\n"); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal( + tctx, + a.out.result, + NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate2 unexpected"); + + return true; +} + +static bool test_ServerReqChallenge_5_repeats( + struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netr_ServerReqChallenge r; + struct netr_Credential credentials1, credentials2, credentials3; + const char *machine_name; + struct dcerpc_binding_handle *b = p->binding_handle; + struct netr_ServerAuthenticate2 a; + uint32_t in_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + uint32_t out_negotiate_flags = 0; + const struct samr_Password *mach_password = NULL; + enum netr_SchannelType sec_chan_type = 0; + struct netlogon_creds_CredentialState *creds = NULL; + const char *account_name = NULL; + + machine_name = cli_credentials_get_workstation(credentials); + mach_password = cli_credentials_get_nt_hash(credentials, tctx); + account_name = cli_credentials_get_username(credentials); + sec_chan_type = cli_credentials_get_secure_channel_type(credentials); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + /* + * Set the first 5 bytes of the client challenge to the same value, + * this should fail CVE-2020-1472(ZeroLogon) + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14497 + */ + credentials1.data[0] = 'A'; + credentials1.data[1] = 'A'; + credentials1.data[2] = 'A'; + credentials1.data[3] = 'A'; + credentials1.data[4] = 'A'; + credentials1.data[5] = 'B'; + credentials1.data[6] = 'C'; + credentials1.data[7] = 'D'; + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok( + tctx, + r.out.result, + "ServerReqChallenge failed"); + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = machine_name; + a.in.negotiate_flags = &in_negotiate_flags; + a.out.negotiate_flags = &out_negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + in_negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate2\n"); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal( + tctx, + a.out.result, + NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate2 unexpected"); + + return true; +} + +static bool test_ServerReqChallenge_4_repeats( + struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netr_ServerReqChallenge r; + struct netr_Credential credentials1, credentials2, credentials3; + const char *machine_name; + struct dcerpc_binding_handle *b = p->binding_handle; + struct netr_ServerAuthenticate2 a; + uint32_t in_negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + uint32_t out_negotiate_flags = 0; + const struct samr_Password *mach_password = NULL; + enum netr_SchannelType sec_chan_type = 0; + struct netlogon_creds_CredentialState *creds = NULL; + const char *account_name = NULL; + + machine_name = cli_credentials_get_workstation(credentials); + mach_password = cli_credentials_get_nt_hash(credentials, tctx); + account_name = cli_credentials_get_username(credentials); + sec_chan_type = cli_credentials_get_secure_channel_type(credentials); + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + /* + * Set the first 4 bytes of the client challenge to the same + * value, this should pass as 5 bytes identical are needed to + * fail for CVE-2020-1472(ZeroLogon) + * + * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14497 + */ + credentials1.data[0] = 'A'; + credentials1.data[1] = 'A'; + credentials1.data[2] = 'A'; + credentials1.data[3] = 'A'; + credentials1.data[4] = 'B'; + credentials1.data[5] = 'C'; + credentials1.data[6] = 'D'; + credentials1.data[7] = 'E'; + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok( + tctx, + r.out.result, + "ServerReqChallenge failed"); + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = machine_name; + a.in.negotiate_flags = &in_negotiate_flags; + a.out.negotiate_flags = &out_negotiate_flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + mach_password, &credentials3, + in_negotiate_flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate2\n"); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal( + tctx, + a.out.result, + NT_STATUS_OK, + "ServerAuthenticate2 unexpected"); + + return true; +} + +/* + * Establish a NetLogon session, using a session key that encrypts the + * target character to zero + */ +static bool test_ServerAuthenticate2_encrypts_to_zero( + struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials, + const char target, + struct netlogon_creds_CredentialState **creds_out) +{ + const char *computer_name = + cli_credentials_get_workstation(machine_credentials); + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate2 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds = NULL; + const struct samr_Password *mach_password; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *account_name = cli_credentials_get_username( + machine_credentials); + uint32_t flags = + NETLOGON_NEG_AUTH2_ADS_FLAGS | + NETLOGON_NEG_SUPPORTS_AES; + enum netr_SchannelType sec_chan_type = + cli_credentials_get_secure_channel_type(machine_credentials); + /* + * Limit the number of attempts to generate a suitable session key. + */ + const unsigned MAX_ITER = 4096; + unsigned i = 0; + + mach_password = cli_credentials_get_nt_hash(machine_credentials, tctx); + + r.in.server_name = NULL; + r.in.computer_name = computer_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + credentials1.data[0] = target; + i = 0; + torture_comment(tctx, "Generating candidate session keys\n"); + do { + TALLOC_FREE(creds); + i++; + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok( + tctx, + r.out.result, + "ServerReqChallenge failed"); + + a.in.server_name = NULL; + a.in.account_name = account_name; + a.in.secure_channel_type = sec_chan_type; + a.in.computer_name = computer_name; + a.in.negotiate_flags = &flags; + a.out.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + + creds = netlogon_creds_client_init( + tctx, + a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, + &credentials2, + mach_password, + &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + } while (credentials3.data[0] != 0 && i < MAX_ITER); + + if (i >= MAX_ITER) { + torture_comment( + tctx, + "Unable to obtain a suitable session key, " + "after [%u] attempts\n", + i); + torture_fail(tctx, "Unable obtain suitable session key"); + } + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerAuthenticate2_r(b, tctx, &a), + "ServerAuthenticate2 failed"); + torture_assert_ntstatus_equal( + tctx, + a.out.result, + NT_STATUS_OK, + "ServerAuthenticate2 unexpected result code"); + + *creds_out = creds; + return true; +} + +/* + try a change password for our machine account +*/ +static bool test_SetPassword(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet r; + const char *password; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential, return_authenticator; + struct samr_Password new_password; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + password = generate_random_password(tctx, 8, 255); + E_md4hash(password, new_password.hash); + + netlogon_creds_des_encrypt(creds, &new_password); + + torture_comment(tctx, "Testing ServerPasswordSet on machine account\n"); + torture_comment(tctx, "Changing machine account password to '%s'\n", + password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet_r(b, tctx, &r), + "ServerPasswordSet failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + /* by changing the machine password twice we test the + credentials chaining fully, and we verify that the server + allows the password to be set to the same value twice in a + row (match win2k3) */ + torture_comment(tctx, + "Testing a second ServerPasswordSet on machine account\n"); + torture_comment(tctx, + "Changing machine account password to '%s' (same as previous run)\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet_r(b, tctx, &r), + "ServerPasswordSet (2) failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet (2) failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + cli_credentials_set_password(machine_credentials, password, CRED_SPECIFIED); + + torture_assert(tctx, + test_SetupCredentials(p, tctx, machine_credentials, &creds), + "ServerPasswordSet failed to actually change the password"); + + return true; +} + +/* + try a change password for our machine account +*/ +static bool test_SetPassword_flags(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials, + uint32_t negotiate_flags) +{ + struct netr_ServerPasswordSet r; + const char *password; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential, return_authenticator; + struct samr_Password new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_SetupCredentials2(p1, tctx, negotiate_flags, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + password = generate_random_password(tctx, 8, 255); + E_md4hash(password, new_password.hash); + + netlogon_creds_des_encrypt(creds, &new_password); + + torture_comment(tctx, "Testing ServerPasswordSet on machine account\n"); + torture_comment(tctx, "Changing machine account password to '%s'\n", + password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet_r(b, tctx, &r), + "ServerPasswordSet failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + /* by changing the machine password twice we test the + credentials chaining fully, and we verify that the server + allows the password to be set to the same value twice in a + row (match win2k3) */ + torture_comment(tctx, + "Testing a second ServerPasswordSet on machine account\n"); + torture_comment(tctx, + "Changing machine account password to '%s' (same as previous run)\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet_r(b, tctx, &r), + "ServerPasswordSet (2) failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet (2) failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + cli_credentials_set_password(machine_credentials, password, CRED_SPECIFIED); + + torture_assert(tctx, + test_SetupCredentials(p, tctx, machine_credentials, &creds), + "ServerPasswordSet failed to actually change the password"); + + return true; +} + + +/* + generate a random password for password change tests +*/ +static DATA_BLOB netlogon_very_rand_pass(TALLOC_CTX *mem_ctx, int len) +{ + int i; + DATA_BLOB password = data_blob_talloc(mem_ctx, NULL, len * 2 /* number of unicode chars */); + generate_random_buffer(password.data, password.length); + + for (i=0; i < len; i++) { + if (((uint16_t *)password.data)[i] == 0) { + ((uint16_t *)password.data)[i] = 1; + } + } + + return password; +} + +/* + try a change password for our machine account +*/ +static bool test_SetPassword2_with_flags(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials, + uint32_t flags) +{ + struct netr_ServerPasswordSet2 r; + const char *password; + DATA_BLOB new_random_pass; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct samr_Password nt_hash; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_SetupCredentials2(p1, tctx, flags, machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + password = generate_random_password(tctx, 8, 255); + encode_pw_buffer(password_buf.data, password, STR_UNICODE); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + } else { + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + } + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment(tctx, "Testing ServerPasswordSet2 on machine account\n"); + torture_comment(tctx, "Changing machine account password to '%s'\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet2 failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + cli_credentials_set_password(machine_credentials, password, CRED_SPECIFIED); + + /* + * As a consequence of CVE-2020-1472(ZeroLogon) + * Samba explicitly disallows the setting of an empty machine account + * password. + * + * Note that this may fail against Windows, and leave a machine account + * with an empty password. + */ + password = ""; + encode_pw_buffer(password_buf.data, password, STR_UNICODE); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + } else { + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + } + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment(tctx, + "Testing ServerPasswordSet2 on machine account\n"); + torture_comment(tctx, + "Changing machine account password to '%s'\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 failed"); + torture_assert_ntstatus_equal( + tctx, + r.out.result, + NT_STATUS_WRONG_PASSWORD, + "ServerPasswordSet2 did not return NT_STATUS_WRONG_PASSWORD"); + + /* now try a random password */ + password = generate_random_password(tctx, 8, 255); + encode_pw_buffer(password_buf.data, password, STR_UNICODE); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + } else { + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + } + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment(tctx, "Testing second ServerPasswordSet2 on machine account\n"); + torture_comment(tctx, "Changing machine account password to '%s'\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 (2) failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet2 (2) failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + /* by changing the machine password twice we test the + credentials chaining fully, and we verify that the server + allows the password to be set to the same value twice in a + row (match win2k3) */ + torture_comment(tctx, + "Testing a second ServerPasswordSet2 on machine account\n"); + torture_comment(tctx, + "Changing machine account password to '%s' (same as previous run)\n", password); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet (3) failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet (3) failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + cli_credentials_set_password(machine_credentials, password, CRED_SPECIFIED); + + torture_assert (tctx, + test_SetupCredentials(p, tctx, machine_credentials, &creds), + "ServerPasswordSet failed to actually change the password"); + + new_random_pass = netlogon_very_rand_pass(tctx, 128); + + /* now try a random stream of bytes for a password */ + set_pw_in_buffer(password_buf.data, &new_random_pass); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + } else { + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + } + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment(tctx, + "Testing a third ServerPasswordSet2 on machine account, with a completely random password\n"); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet (3) failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordSet (3) failed"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + mdfour(nt_hash.hash, new_random_pass.data, new_random_pass.length); + + cli_credentials_set_password(machine_credentials, NULL, CRED_UNINITIALISED); + cli_credentials_set_nt_hash(machine_credentials, &nt_hash, CRED_SPECIFIED); + + torture_assert (tctx, + test_SetupCredentials(p, tctx, machine_credentials, &creds), + "ServerPasswordSet failed to actually change the password"); + + return true; +} + +/* + try to change the password of our machine account using a buffer of all zeros, + and a session key that encrypts that to all zeros. + +Note: The test does use sign and seal, it's purpose is to exercise + the detection code in dcesrv_netr_ServerPasswordSet2 +*/ +static bool test_SetPassword2_encrypted_to_all_zeros( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_ServerAuthenticate2_encrypts_to_zero( + tctx, + p1, + machine_credentials, + '\0', + &creds)) { + + return false; + } + + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + ZERO_STRUCT(password_buf); + + if (!(creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES)) { + torture_fail(tctx, "NETLOGON_NEG_SUPPORTS_AES not set"); + } + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + if(!all_zero(password_buf.data, 516)) { + torture_fail(tctx, "Password did not encrypt to all zeros\n"); + } + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + torture_assert_int_equal( + tctx, + new_password.length, + 0, + "Length should have encrypted to 0"); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 zero length check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, ""); + + return true; +} + +/* + * Choose a session key that encrypts a password of all zeros to all zeros. + * Then try to set the password, using a zeroed buffer, with a non zero + * length. + * + * This exercises the password self encryption check. + * + * Note: The test does use sign and seal, it's purpose is to exercise + * the detection code in dcesrv_netr_ServerPasswordSet2 +*/ +static bool test_SetPassword2_password_encrypts_to_zero( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_ServerAuthenticate2_encrypts_to_zero( + tctx, + p1, + machine_credentials, + 0x00, + &creds)) { + + return false; + } + + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + ZERO_STRUCT(password_buf); + SIVAL(password_buf.data, 512, 512); + + if (!(creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES)) { + torture_fail(tctx, "NETLOGON_NEG_SUPPORTS_AES not set"); + } + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 password encrypts to zero check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, ""); + + return true; +} + +/* + * Check that an all zero confounder, that encrypts to all zeros is + * rejected. + * + * Note: The test does use sign and seal, it's purpose is to exercise + * the detection code in dcesrv_netr_ServerPasswordSet2 + */ +static bool test_SetPassword2_confounder( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_ServerAuthenticate2_encrypts_to_zero( + tctx, + p1, + machine_credentials, + '\0', + &creds)) { + + return false; + } + + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + ZERO_STRUCT(password_buf); + password_buf.data[511] = 'A'; + SIVAL(password_buf.data, 512, 2); + + if (!(creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES)) { + torture_fail(tctx, "NETLOGON_NEG_SUPPORTS_AES not set"); + } + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 confounder check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, ""); + + return true; +} + +/* + * try a change password for our machine account, using an all zero + * request. This should fail on the zero length check. + * + * Note: This test uses ARC4 encryption to exercise the desired check. + */ +static bool test_SetPassword2_all_zeros( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + uint32_t flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; /* no AES desired here */ + + if (!test_SetupCredentials2( + p1, + tctx, + flags, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) + { + return false; + } + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + ZERO_STRUCT(password_buf.data); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + torture_fail(tctx, "NETLOGON_NEG_SUPPORTS_AES enabled\n"); + } + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment( + tctx, + "Testing ServerPasswordSet2 on machine account\n"); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 zero length check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, ""); + + return true; +} + +/* + try a change password for our machine account, using a maximum length + password +*/ +static bool test_SetPassword2_maximum_length_password( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + uint32_t flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + DATA_BLOB new_random_pass = data_blob_null; + + if (!test_SetupCredentials2( + p1, + tctx, + flags, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) + { + return false; + } + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + new_random_pass = netlogon_very_rand_pass(tctx, 256); + set_pw_in_buffer(password_buf.data, &new_random_pass); + SIVAL(password_buf.data, 512, 512); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, password_buf.data, 516); + } else { + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + } + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment( + tctx, + "Testing ServerPasswordSet2 on machine account\n"); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 zero length check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_OK, ""); + + return true; +} + +/* + try a change password for our machine account, using a password of + all zeros, and a non zero password length. + + This test relies on the buffer being encrypted with ARC4, to + trigger the appropriate check in the rpc server code +*/ +static bool test_SetPassword2_all_zero_password( + struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordSet2 r; + struct netlogon_creds_CredentialState *creds; + struct samr_CryptPassword password_buf; + struct netr_Authenticator credential, return_authenticator; + struct netr_CryptPassword new_password; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + uint32_t flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; /* no AES desired here */ + + if (!test_SetupCredentials2( + p1, + tctx, + flags, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) + { + return false; + } + if (!test_SetupCredentialsPipe( + p1, + tctx, + machine_credentials, + creds, + DCERPC_SIGN | DCERPC_SEAL, + &p)) + { + return false; + } + b = p->binding_handle; + + r.in.server_name = talloc_asprintf( + tctx, + "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + ZERO_STRUCT(password_buf.data); + SIVAL(password_buf.data, 512, 128); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + torture_fail(tctx, "NETLOGON_NEG_SUPPORTS_AES set"); + } + netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); + + memcpy(new_password.data, password_buf.data, 512); + new_password.length = IVAL(password_buf.data, 512); + + torture_comment( + tctx, + "Testing ServerPasswordSet2 on machine account\n"); + + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok( + tctx, + dcerpc_netr_ServerPasswordSet2_r(b, tctx, &r), + "ServerPasswordSet2 all zero password check failed"); + torture_assert_ntstatus_equal( + tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, ""); + + return true; +} + + +static bool test_SetPassword2(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + return test_SetPassword2_with_flags(tctx, p, machine_credentials, NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_SetPassword2_AES(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + return test_SetPassword2_with_flags(tctx, p, machine_credentials, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +static bool test_GetPassword(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerPasswordGet r; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential; + NTSTATUS status; + struct netr_Authenticator return_authenticator; + struct samr_Password password; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + netlogon_creds_client_authenticator(creds, &credential); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.out.return_authenticator = &return_authenticator; + r.out.password = &password; + + status = dcerpc_netr_ServerPasswordGet_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ServerPasswordGet"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerPasswordGet"); + + return true; +} + +static bool test_GetTrustPasswords(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_ServerTrustPasswordsGet r; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential; + struct netr_Authenticator return_authenticator; + struct samr_Password password, password2; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + netlogon_creds_client_authenticator(creds, &credential); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.out.return_authenticator = &return_authenticator; + r.out.new_owf_password = &password; + r.out.old_owf_password = &password2; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerTrustPasswordsGet_r(b, tctx, &r), + "ServerTrustPasswordsGet failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerTrustPasswordsGet failed"); + + return true; +} + +/* + try a netlogon SamLogon +*/ +static bool test_netlogon_ops_args(struct dcerpc_pipe *p, struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState *creds, + bool null_domain) +{ + NTSTATUS status; + struct netr_LogonSamLogon r; + struct netr_Authenticator auth, auth2; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative; + struct netr_NetworkInfo ninfo; + DATA_BLOB names_blob, chal, lm_resp, nt_resp; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + int flags = CLI_CRED_NTLM_AUTH; + if (lpcfg_client_lanman_auth(tctx->lp_ctx)) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (lpcfg_client_ntlmv2_auth(tctx->lp_ctx) && !null_domain) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + + cli_credentials_get_ntlm_username_domain(samba_cmdline_get_creds(), + tctx, + &ninfo.identity_info.account_name.string, + &ninfo.identity_info.domain_name.string); + + if (null_domain) { + ninfo.identity_info.domain_name.string = NULL; + } + + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + chal = data_blob_const(ninfo.challenge, + sizeof(ninfo.challenge)); + + names_blob = NTLMv2_generate_names_blob(tctx, cli_credentials_get_workstation(credentials), + cli_credentials_get_domain(credentials)); + + status = cli_credentials_get_ntlm_response( + samba_cmdline_get_creds(), tctx, + &flags, + chal, + NULL, /* server_timestamp */ + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(tctx, status, "cli_credentials_get_ntlm_response failed"); + + ninfo.lm.data = lm_resp.data; + ninfo.lm.length = lm_resp.length; + + ninfo.nt.data = nt_resp.data; + ninfo.nt.length = nt_resp.length; + + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.workstation.string = cli_credentials_get_workstation(credentials); + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(credentials); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonNetworkInformation; + r.in.logon = &logon; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + + d_printf("Testing LogonSamLogon with name %s\n", ninfo.identity_info.account_name.string); + + for (i=2;i<=3;i++) { + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + + r.in.validation_level = i; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + } + + /* this makes sure we get the unmarshalling right for invalid levels */ + for (i=52;i<53;i++) { + ZERO_STRUCT(auth2); + /* the authenticator should be ignored by the server */ + generate_random_buffer((uint8_t *) &auth, sizeof(auth)); + + r.in.validation_level = i; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, + NT_STATUS_INVALID_INFO_CLASS, + "LogonSamLogon failed"); + + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + torture_assert(tctx, + all_zero((uint8_t *)&auth2, sizeof(auth2)), + "Return authenticator non zero"); + } + + for (i=2;i<=3;i++) { + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + + r.in.validation_level = i; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + } + + r.in.logon_level = 52; + + for (i=2;i<=3;i++) { + ZERO_STRUCT(auth2); + /* the authenticator should be ignored by the server */ + generate_random_buffer((uint8_t *) &auth, sizeof(auth)); + + r.in.validation_level = i; + + torture_comment(tctx, "Testing SamLogon with validation level %d and a NULL credential\n", i); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_PARAMETER, + "LogonSamLogon expected INVALID_PARAMETER"); + + torture_assert(tctx, + all_zero((uint8_t *)&auth2, sizeof(auth2)), + "Return authenticator non zero"); + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + } + + r.in.credential = NULL; + + for (i=2;i<=3;i++) { + ZERO_STRUCT(auth2); + + r.in.validation_level = i; + + torture_comment(tctx, "Testing SamLogon with validation level %d and a NULL credential\n", i); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_PARAMETER, + "LogonSamLogon expected INVALID_PARAMETER"); + + torture_assert(tctx, + all_zero((uint8_t *)&auth2, sizeof(auth2)), + "Return authenticator non zero"); + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + } + + r.in.logon_level = NetlogonNetworkInformation; + r.in.credential = &auth; + + for (i=2;i<=3;i++) { + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + + r.in.validation_level = i; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + torture_assert_int_equal(tctx, *r.out.authoritative, 1, + "LogonSamLogon invalid *r.out.authoritative"); + } + + return true; +} + +bool test_netlogon_ops(struct dcerpc_pipe *p, struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState *creds) +{ + return test_netlogon_ops_args(p, tctx, credentials, creds, false); +} + +/* + try a netlogon GetCapabilities +*/ +bool test_netlogon_capabilities(struct dcerpc_pipe *p, struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + struct netr_LogonGetCapabilities r; + union netr_Capabilities capabilities; + struct netr_Authenticator auth, return_auth; + struct netlogon_creds_CredentialState tmp_creds; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(credentials); + r.in.credential = &auth; + r.in.return_authenticator = &return_auth; + r.in.query_level = 1; + r.out.capabilities = &capabilities; + r.out.return_authenticator = &return_auth; + + torture_comment(tctx, "Testing LogonGetCapabilities with query_level=0\n"); + + r.in.query_level = 0; + ZERO_STRUCT(return_auth); + + /* + * we need to operate on a temporary copy of creds + * because dcerpc_netr_LogonGetCapabilities with + * an unknown query level returns DCERPC_NCA_S_FAULT_INVALID_TAG + * => NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE + * without looking a the authenticator. + */ + tmp_creds = *creds; + netlogon_creds_client_authenticator(&tmp_creds, &auth); + + status = dcerpc_netr_LogonGetCapabilities_r(b, tctx, &r); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE, + "LogonGetCapabilities query_level=0 failed"); + + torture_comment(tctx, "Testing LogonGetCapabilities with query_level=3\n"); + + r.in.query_level = 3; + ZERO_STRUCT(return_auth); + + /* + * we need to operate on a temporary copy of creds + * because dcerpc_netr_LogonGetCapabilities with + * an unknown query level returns DCERPC_NCA_S_FAULT_INVALID_TAG + * => NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE + * without looking a the authenticator. + */ + tmp_creds = *creds; + netlogon_creds_client_authenticator(&tmp_creds, &auth); + + status = dcerpc_netr_LogonGetCapabilities_r(b, tctx, &r); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE, + "LogonGetCapabilities query_level=0 failed"); + + torture_comment(tctx, "Testing LogonGetCapabilities with query_level=1\n"); + + r.in.query_level = 1; + ZERO_STRUCT(return_auth); + + /* + * we need to operate on a temporary copy of creds + * because dcerpc_netr_LogonGetCapabilities was + * dcerpc_netr_DummyFunction and returns NT_STATUS_NOT_IMPLEMENTED + * without looking a the authenticator. + */ + tmp_creds = *creds; + netlogon_creds_client_authenticator(&tmp_creds, &auth); + + status = dcerpc_netr_LogonGetCapabilities_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonGetCapabilities failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_IMPLEMENTED)) { + return true; + } + + *creds = tmp_creds; + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + + torture_assert_int_equal(tctx, creds->negotiate_flags, + capabilities.server_capabilities, + "negotiate flags"); + + torture_comment(tctx, "Testing LogonGetCapabilities with query_level=2\n"); + + r.in.query_level = 2; + ZERO_STRUCT(return_auth); + + /* + * we need to operate on a temporary copy of creds + * because dcerpc_netr_LogonGetCapabilities with + * an query level 2 may returns DCERPC_NCA_S_FAULT_INVALID_TAG + * => NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE + * without looking a the authenticator. + */ + tmp_creds = *creds; + netlogon_creds_client_authenticator(&tmp_creds, &auth); + + status = dcerpc_netr_LogonGetCapabilities_r(b, tctx, &r); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) { + /* + * an server without KB5028166 returns + * DCERPC_NCA_S_FAULT_INVALID_TAG => + * NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE + */ + return true; + } + torture_assert_ntstatus_ok(tctx, status, "LogonGetCapabilities query_level=2 failed"); + + *creds = tmp_creds; + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + + torture_assert_int_equal(tctx, creds->negotiate_flags, + capabilities.server_capabilities, + "negotiate flags"); + + return true; +} + +/* + try a netlogon SamLogon +*/ +static bool test_SamLogon(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netlogon_creds_CredentialState *creds; + + if (!test_SetupCredentials(p, tctx, credentials, &creds)) { + return false; + } + + return test_netlogon_ops(p, tctx, credentials, creds); +} + +static bool test_invalidAuthenticate2(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netlogon_creds_CredentialState *creds; + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + + torture_comment(tctx, "Testing invalidAuthenticate2\n"); + + if (!test_SetupCredentials2(p, tctx, flags, + credentials, + cli_credentials_get_secure_channel_type(credentials), + &creds)) { + return false; + } + + if (!test_SetupCredentials2ex(p, tctx, flags, + credentials, + "1234567890123456", + cli_credentials_get_secure_channel_type(credentials), + STATUS_BUFFER_OVERFLOW, + &creds)) { + return false; + } + + if (!test_SetupCredentials2ex(p, tctx, flags, + credentials, + "123456789012345", + cli_credentials_get_secure_channel_type(credentials), + NT_STATUS_OK, + &creds)) { + return false; + } + + return true; +} + +static bool test_ServerReqChallengeGlobal(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b1 = p1->binding_handle; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b2 = NULL; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b2 = p2->binding_handle; + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 on b2\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b2, tctx, &a), + "ServerAuthenticate3 failed on b2"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed on b2"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + return true; +} + +/* + * Test the re-use of the challenge is not possible on a third + * connection, after first useing it second one. + */ + +static bool test_ServerReqChallengeReuseGlobal(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b1 = p1->binding_handle; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b2 = NULL; + struct dcerpc_pipe *p3 = NULL; + struct dcerpc_binding_handle *b3 = NULL; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b2 = p2->binding_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p3, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b3 = p3->binding_handle; + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 on b2\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b2, tctx, &a), + "ServerAuthenticate3 failed on b2"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed on b2"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + /* We have to re-run this part */ + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b3, tctx, &a), + "ServerAuthenticate3 failed on b3"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b3, due to credential reuse"); + return true; +} + +/* + * Test if use of the per-pipe challenge will wipe out the globally cached challenge + */ +static bool test_ServerReqChallengeReuseGlobal2(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b1 = p1->binding_handle; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b2 = NULL; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b2 = p2->binding_handle; + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 on b2\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b1, tctx, &a), + "ServerAuthenticate3 failed on b"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed on b"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + /* We have to re-run this part */ + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b2, tctx, &a), + "ServerAuthenticate3 failed on b2"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b2, due to credential reuse"); + return true; +} + +/* + * Test if use of the globally cached challenge will wipe out the + * per-pipe challenge + */ +static bool test_ServerReqChallengeReuseGlobal3(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b1 = p1->binding_handle; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b2 = NULL; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b2 = p2->binding_handle; + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 on b2\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b2, tctx, &a), + "ServerAuthenticate3 failed on b2"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed on b"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + /* We have to re-run this part */ + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b1, tctx, &a), + "ServerAuthenticate3 failed on b1"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b1, due to credential reuse"); + return true; +} + +/* + * Test if more than one globally cached challenge works + */ +static bool test_ServerReqChallengeReuseGlobal4(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials1_random, + credentials2, credentials3, credentials_discard; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b1 = p1->binding_handle; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b2 = NULL; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p2, p1->binding, + &ndr_table_netlogon, + machine_credentials, + tctx->ev, tctx->lp_ctx), + "dcerpc_pipe_connect_b failed"); + b2 = p2->binding_handle; + + r.in.server_name = NULL; + r.in.computer_name = "CHALTEST1"; + r.in.credentials = &credentials1_random; + r.out.return_credentials = &credentials_discard; + + netlogon_creds_random_challenge(&credentials1_random); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + /* Now ask for the actual client name */ + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + r.in.server_name = NULL; + r.in.computer_name = "CHALTEST2"; + r.in.credentials = &credentials1_random; + r.out.return_credentials = &credentials_discard; + + netlogon_creds_random_challenge(&credentials1_random); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b1, tctx, &r), + "ServerReqChallenge failed on b1"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 on b2 (must use global credentials)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b2, tctx, &a), + "ServerAuthenticate3 failed on b2"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed on b2"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + /* We have to re-run this part */ + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b1, tctx, &a), + "ServerAuthenticate3 failed on b1"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b1, due to credential reuse"); + return true; +} + +static bool test_ServerReqChallengeReuse(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + uint32_t flags = NETLOGON_NEG_AUTH2_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential credentials1, credentials2, credentials3; + struct netlogon_creds_CredentialState *creds; + struct samr_Password mach_password; + uint32_t rid; + const char *machine_name; + const char *plain_pass; + struct dcerpc_binding_handle *b = p->binding_handle; + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert(tctx, machine_name != NULL, "machine_name"); + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert(tctx, plain_pass != NULL, "plain_pass"); + + torture_comment(tctx, "Testing ServerReqChallenge on b1\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &credentials1; + r.out.return_credentials = &credentials2; + + netlogon_creds_random_challenge(&credentials1); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed on b1"); + + E_md4hash(plain_pass, mach_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &flags; + a.in.credentials = &credentials3; + a.out.return_credentials = &credentials3; + a.out.negotiate_flags = &flags; + a.out.rid = &rid; + + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_ok(tctx, a.out.result, "ServerAuthenticate3 failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &credentials3), "Credential chaining failed"); + + /* We have to re-run this part */ + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b3, due to credential reuse"); + + ZERO_STRUCT(credentials1.data); + ZERO_STRUCT(credentials2.data); + creds = netlogon_creds_client_init(tctx, a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &credentials1, &credentials2, + &mach_password, &credentials3, + flags); + + torture_assert(tctx, creds != NULL, "memory allocation"); + + torture_comment(tctx, "Testing ServerAuthenticate3 with zero'ed challenge\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a), + "ServerAuthenticate3 failed"); + torture_assert_ntstatus_equal(tctx, a.out.result, NT_STATUS_ACCESS_DENIED, + "ServerAuthenticate3 should have failed on b3, due to credential reuse"); + return true; +} + +static bool test_SamLogon_NULL_domain(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + struct netlogon_creds_CredentialState *creds; + + if (!test_SetupCredentials(p, tctx, credentials, &creds)) { + return false; + } + + return test_netlogon_ops_args(p, tctx, credentials, creds, true); +} + +/* we remember the sequence numbers so we can easily do a DatabaseDelta */ +static uint64_t sequence_nums[3]; + +/* + try a netlogon DatabaseSync +*/ +static bool test_DatabaseSync(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_DatabaseSync r; + struct netlogon_creds_CredentialState *creds; + const uint32_t database_ids[] = {SAM_DATABASE_DOMAIN, SAM_DATABASE_BUILTIN, SAM_DATABASE_PRIVS}; + int i; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + struct netr_Authenticator credential, return_authenticator; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.preferredmaximumlength = (uint32_t)-1; + r.in.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + r.out.return_authenticator = &return_authenticator; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + + uint32_t sync_context = 0; + + r.in.database_id = database_ids[i]; + r.in.sync_context = &sync_context; + r.out.sync_context = &sync_context; + + torture_comment(tctx, "Testing DatabaseSync of id %d\n", r.in.database_id); + + do { + netlogon_creds_client_authenticator(creds, &credential); + + r.in.credential = &credential; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_DatabaseSync_r(b, tctx, &r), + "DatabaseSync failed"); + if (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) + break; + + /* Native mode servers don't do this */ + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_SUPPORTED)) { + return true; + } + torture_assert_ntstatus_ok(tctx, r.out.result, "DatabaseSync"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + if (delta_enum_array && + delta_enum_array->num_deltas > 0 && + delta_enum_array->delta_enum[0].delta_type == NETR_DELTA_DOMAIN && + delta_enum_array->delta_enum[0].delta_union.domain) { + sequence_nums[r.in.database_id] = + delta_enum_array->delta_enum[0].delta_union.domain->sequence_num; + torture_comment(tctx, "\tsequence_nums[%d]=%llu\n", + r.in.database_id, + (unsigned long long)sequence_nums[r.in.database_id]); + } + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + + +/* + try a netlogon DatabaseDeltas +*/ +static bool test_DatabaseDeltas(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_DatabaseDeltas r; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential; + struct netr_Authenticator return_authenticator; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + const uint32_t database_ids[] = {0, 1, 2}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.preferredmaximumlength = (uint32_t)-1; + ZERO_STRUCT(r.in.return_authenticator); + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + r.in.database_id = database_ids[i]; + r.in.sequence_num = &sequence_nums[r.in.database_id]; + + if (*r.in.sequence_num == 0) continue; + + *r.in.sequence_num -= 1; + + torture_comment(tctx, "Testing DatabaseDeltas of id %d at %llu\n", + r.in.database_id, (unsigned long long)*r.in.sequence_num); + + do { + netlogon_creds_client_authenticator(creds, &credential); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_DatabaseDeltas_r(b, tctx, &r), + "DatabaseDeltas failed"); + if (NT_STATUS_EQUAL(r.out.result, + NT_STATUS_SYNCHRONIZATION_REQUIRED)) { + torture_comment(tctx, "not considering %s to be an error\n", + nt_errstr(r.out.result)); + return true; + } + if (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) + break; + + torture_assert_ntstatus_ok(tctx, r.out.result, "DatabaseDeltas"); + + if (!netlogon_creds_client_check(creds, &return_authenticator.cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + (*r.in.sequence_num)++; + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + +static bool test_DatabaseRedo(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_DatabaseRedo r; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator credential; + struct netr_Authenticator return_authenticator; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + struct netr_ChangeLogEntry e; + struct dom_sid null_sid, *sid; + int i,d; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(null_sid); + + sid = dom_sid_parse_talloc(tctx, "S-1-5-21-1111111111-2222222222-333333333-500"); + + { + + struct { + uint32_t rid; + uint16_t flags; + uint8_t db_index; + uint8_t delta_type; + struct dom_sid sid; + const char *name; + NTSTATUS expected_error; + uint32_t expected_num_results; + uint8_t expected_delta_type_1; + uint8_t expected_delta_type_2; + const char *comment; + } changes[] = { + + /* SAM_DATABASE_DOMAIN */ + + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_MODIFY_COUNT, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_SYNCHRONIZATION_REQUIRED, + .expected_num_results = 0, + .comment = "NETR_DELTA_MODIFY_COUNT" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = 0, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DOMAIN, + .comment = "NULL DELTA" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_DOMAIN, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DOMAIN, + .comment = "NETR_DELTA_DOMAIN" + }, + { + .rid = DOMAIN_RID_ADMINISTRATOR, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_USER, + .comment = "NETR_DELTA_USER by rid 500" + }, + { + .rid = DOMAIN_RID_GUEST, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_USER, + .comment = "NETR_DELTA_USER by rid 501" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_USER, + .sid = *sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_USER, + .comment = "NETR_DELTA_USER by sid and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_USER, + .comment = "NETR_DELTA_USER by null_sid and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_NAME_INCLUDED, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = "administrator", + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_USER, + .comment = "NETR_DELTA_USER by name 'administrator'" + }, + { + .rid = DOMAIN_RID_ADMINS, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_GROUP, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 2, + .expected_delta_type_1 = NETR_DELTA_GROUP, + .expected_delta_type_2 = NETR_DELTA_GROUP_MEMBER, + .comment = "NETR_DELTA_GROUP by rid 512" + }, + { + .rid = DOMAIN_RID_ADMINS, + .flags = 0, + .db_index = SAM_DATABASE_DOMAIN, + .delta_type = NETR_DELTA_GROUP_MEMBER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 2, + .expected_delta_type_1 = NETR_DELTA_GROUP, + .expected_delta_type_2 = NETR_DELTA_GROUP_MEMBER, + .comment = "NETR_DELTA_GROUP_MEMBER by rid 512" + }, + + + /* SAM_DATABASE_BUILTIN */ + + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_MODIFY_COUNT, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_SYNCHRONIZATION_REQUIRED, + .expected_num_results = 0, + .comment = "NETR_DELTA_MODIFY_COUNT" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_DOMAIN, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DOMAIN, + .comment = "NETR_DELTA_DOMAIN" + }, + { + .rid = DOMAIN_RID_ADMINISTRATOR, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_USER, + .comment = "NETR_DELTA_USER by rid 500" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_USER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_USER, + .comment = "NETR_DELTA_USER" + }, + { + .rid = 544, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_ALIAS, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 2, + .expected_delta_type_1 = NETR_DELTA_ALIAS, + .expected_delta_type_2 = NETR_DELTA_ALIAS_MEMBER, + .comment = "NETR_DELTA_ALIAS by rid 544" + }, + { + .rid = 544, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_ALIAS_MEMBER, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 2, + .expected_delta_type_1 = NETR_DELTA_ALIAS, + .expected_delta_type_2 = NETR_DELTA_ALIAS_MEMBER, + .comment = "NETR_DELTA_ALIAS_MEMBER by rid 544" + }, + { + .rid = 544, + .flags = 0, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = 0, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DOMAIN, + .comment = "NULL DELTA by rid 544" + }, + { + .rid = 544, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = 0, + .sid = *dom_sid_parse_talloc(tctx, "S-1-5-32-544"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DOMAIN, + .comment = "NULL DELTA by rid 544 sid S-1-5-32-544 and flags" + }, + { + .rid = 544, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_ALIAS, + .sid = *dom_sid_parse_talloc(tctx, "S-1-5-32-544"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 2, + .expected_delta_type_1 = NETR_DELTA_ALIAS, + .expected_delta_type_2 = NETR_DELTA_ALIAS_MEMBER, + .comment = "NETR_DELTA_ALIAS by rid 544 and sid S-1-5-32-544 and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_BUILTIN, + .delta_type = NETR_DELTA_ALIAS, + .sid = *dom_sid_parse_talloc(tctx, "S-1-5-32-544"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_ALIAS, + .comment = "NETR_DELTA_ALIAS by sid S-1-5-32-544 and flags" + }, + + /* SAM_DATABASE_PRIVS */ + + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = 0, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_ACCESS_DENIED, + .expected_num_results = 0, + .comment = "NULL DELTA" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_MODIFY_COUNT, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_SYNCHRONIZATION_REQUIRED, + .expected_num_results = 0, + .comment = "NETR_DELTA_MODIFY_COUNT" + }, + { + .rid = 0, + .flags = 0, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_POLICY, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_POLICY, + .comment = "NETR_DELTA_POLICY" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_POLICY, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_POLICY, + .comment = "NETR_DELTA_POLICY by null sid and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_POLICY, + .sid = *dom_sid_parse_talloc(tctx, "S-1-5-32"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_POLICY, + .comment = "NETR_DELTA_POLICY by sid S-1-5-32 and flags" + }, + { + .rid = DOMAIN_RID_ADMINISTRATOR, + .flags = 0, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_ACCOUNT, + .sid = null_sid, + .name = NULL, + .expected_error = NT_STATUS_SYNCHRONIZATION_REQUIRED, /* strange */ + .expected_num_results = 0, + .comment = "NETR_DELTA_ACCOUNT by rid 500" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_ACCOUNT, + .sid = *dom_sid_parse_talloc(tctx, "S-1-1-0"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_ACCOUNT, + .comment = "NETR_DELTA_ACCOUNT by sid S-1-1-0 and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED | + NETR_CHANGELOG_IMMEDIATE_REPL_REQUIRED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_ACCOUNT, + .sid = *dom_sid_parse_talloc(tctx, "S-1-1-0"), + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_ACCOUNT, + .comment = "NETR_DELTA_ACCOUNT by sid S-1-1-0 and 2 flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_SID_INCLUDED | + NETR_CHANGELOG_NAME_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_ACCOUNT, + .sid = *dom_sid_parse_talloc(tctx, "S-1-1-0"), + .name = NULL, + .expected_error = NT_STATUS_INVALID_PARAMETER, + .expected_num_results = 0, + .comment = "NETR_DELTA_ACCOUNT by sid S-1-1-0 and invalid flags" + }, + { + .rid = DOMAIN_RID_ADMINISTRATOR, + .flags = NETR_CHANGELOG_SID_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_ACCOUNT, + .sid = *sid, + .name = NULL, + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_ACCOUNT, + .comment = "NETR_DELTA_ACCOUNT by rid 500, sid and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_NAME_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_SECRET, + .sid = null_sid, + .name = "IsurelydontexistIhope", + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_DELETE_SECRET, + .comment = "NETR_DELTA_SECRET by name 'IsurelydontexistIhope' and flags" + }, + { + .rid = 0, + .flags = NETR_CHANGELOG_NAME_INCLUDED, + .db_index = SAM_DATABASE_PRIVS, + .delta_type = NETR_DELTA_SECRET, + .sid = null_sid, + .name = "G$BCKUPKEY_P", + .expected_error = NT_STATUS_OK, + .expected_num_results = 1, + .expected_delta_type_1 = NETR_DELTA_SECRET, + .comment = "NETR_DELTA_SECRET by name 'G$BCKUPKEY_P' and flags" + } + }; + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.return_authenticator = &return_authenticator; + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (d=0; d<3; d++) { + const char *database = NULL; + + switch (d) { + case 0: + database = "SAM"; + break; + case 1: + database = "BUILTIN"; + break; + case 2: + database = "LSA"; + break; + default: + break; + } + + torture_comment(tctx, "Testing DatabaseRedo\n"); + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + for (i=0;i<ARRAY_SIZE(changes);i++) { + + if (d != changes[i].db_index) { + continue; + } + + netlogon_creds_client_authenticator(creds, &credential); + + r.in.credential = &credential; + + e.serial_number1 = 0; + e.serial_number2 = 0; + e.object_rid = changes[i].rid; + e.flags = changes[i].flags; + e.db_index = changes[i].db_index; + e.delta_type = changes[i].delta_type; + + switch (changes[i].flags & (NETR_CHANGELOG_NAME_INCLUDED | NETR_CHANGELOG_SID_INCLUDED)) { + case NETR_CHANGELOG_SID_INCLUDED: + e.object.object_sid = changes[i].sid; + break; + case NETR_CHANGELOG_NAME_INCLUDED: + e.object.object_name = changes[i].name; + break; + default: + break; + } + + r.in.change_log_entry = e; + + torture_comment(tctx, "Testing DatabaseRedo with database %s and %s\n", + database, changes[i].comment); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_DatabaseRedo_r(b, tctx, &r), + "DatabaseRedo failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_SUPPORTED)) { + return true; + } + + torture_assert_ntstatus_equal(tctx, r.out.result, changes[i].expected_error, changes[i].comment); + if (delta_enum_array) { + torture_assert_int_equal(tctx, + delta_enum_array->num_deltas, + changes[i].expected_num_results, + changes[i].comment); + if (delta_enum_array->num_deltas > 0) { + torture_assert_int_equal(tctx, + delta_enum_array->delta_enum[0].delta_type, + changes[i].expected_delta_type_1, + changes[i].comment); + } + if (delta_enum_array->num_deltas > 1) { + torture_assert_int_equal(tctx, + delta_enum_array->delta_enum[1].delta_type, + changes[i].expected_delta_type_2, + changes[i].comment); + } + } + + if (!netlogon_creds_client_check(creds, &return_authenticator.cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + } + } + } + } + + return true; +} + +/* + try a netlogon AccountDeltas +*/ +static bool test_AccountDeltas(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_AccountDeltas r; + struct netlogon_creds_CredentialState *creds; + + struct netr_AccountBuffer buffer; + uint32_t count_returned = 0; + uint32_t total_entries = 0; + struct netr_UAS_INFO_0 recordid; + struct netr_Authenticator return_authenticator; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.return_authenticator = &return_authenticator; + netlogon_creds_client_authenticator(creds, &r.in.credential); + ZERO_STRUCT(r.in.uas); + r.in.count=10; + r.in.level=0; + r.in.buffersize=100; + r.out.buffer = &buffer; + r.out.count_returned = &count_returned; + r.out.total_entries = &total_entries; + r.out.recordid = &recordid; + r.out.return_authenticator = &return_authenticator; + + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_AccountDeltas_r(b, tctx, &r), + "AccountDeltas failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_IMPLEMENTED, "AccountDeltas"); + + return true; +} + +/* + try a netlogon AccountSync +*/ +static bool test_AccountSync(struct torture_context *tctx, struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_AccountSync r; + struct netlogon_creds_CredentialState *creds; + + struct netr_AccountBuffer buffer; + uint32_t count_returned = 0; + uint32_t total_entries = 0; + uint32_t next_reference = 0; + struct netr_UAS_INFO_0 recordid; + struct netr_Authenticator return_authenticator; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(recordid); + ZERO_STRUCT(return_authenticator); + + if (!test_SetupCredentials(p, tctx, machine_credentials, &creds)) { + return false; + } + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.return_authenticator = &return_authenticator; + netlogon_creds_client_authenticator(creds, &r.in.credential); + r.in.recordid = &recordid; + r.in.reference=0; + r.in.level=0; + r.in.buffersize=100; + r.out.buffer = &buffer; + r.out.count_returned = &count_returned; + r.out.total_entries = &total_entries; + r.out.next_reference = &next_reference; + r.out.recordid = &recordid; + r.out.return_authenticator = &return_authenticator; + + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_AccountSync_r(b, tctx, &r), + "AccountSync failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_IMPLEMENTED, "AccountSync"); + + return true; +} + +/* + try a netlogon GetDcName +*/ +static bool test_GetDcName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct netr_GetDcName r; + const char *dcname = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.domainname = lpcfg_workgroup(tctx->lp_ctx); + r.out.dcname = &dcname; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_GetDcName_r(b, tctx, &r), + "GetDcName failed"); + torture_assert_werr_ok(tctx, r.out.result, "GetDcName failed"); + + torture_comment(tctx, "\tDC is at '%s'\n", dcname); + + return true; +} + +static const char *function_code_str(TALLOC_CTX *mem_ctx, + enum netr_LogonControlCode function_code) +{ + switch (function_code) { + case NETLOGON_CONTROL_QUERY: + return "NETLOGON_CONTROL_QUERY"; + case NETLOGON_CONTROL_REPLICATE: + return "NETLOGON_CONTROL_REPLICATE"; + case NETLOGON_CONTROL_SYNCHRONIZE: + return "NETLOGON_CONTROL_SYNCHRONIZE"; + case NETLOGON_CONTROL_PDC_REPLICATE: + return "NETLOGON_CONTROL_PDC_REPLICATE"; + case NETLOGON_CONTROL_REDISCOVER: + return "NETLOGON_CONTROL_REDISCOVER"; + case NETLOGON_CONTROL_TC_QUERY: + return "NETLOGON_CONTROL_TC_QUERY"; + case NETLOGON_CONTROL_TRANSPORT_NOTIFY: + return "NETLOGON_CONTROL_TRANSPORT_NOTIFY"; + case NETLOGON_CONTROL_FIND_USER: + return "NETLOGON_CONTROL_FIND_USER"; + case NETLOGON_CONTROL_CHANGE_PASSWORD: + return "NETLOGON_CONTROL_CHANGE_PASSWORD"; + case NETLOGON_CONTROL_TC_VERIFY: + return "NETLOGON_CONTROL_TC_VERIFY"; + case NETLOGON_CONTROL_FORCE_DNS_REG: + return "NETLOGON_CONTROL_FORCE_DNS_REG"; + case NETLOGON_CONTROL_QUERY_DNS_REG: + return "NETLOGON_CONTROL_QUERY_DNS_REG"; + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + return "NETLOGON_CONTROL_BACKUP_CHANGE_LOG"; + case NETLOGON_CONTROL_TRUNCATE_LOG: + return "NETLOGON_CONTROL_TRUNCATE_LOG"; + case NETLOGON_CONTROL_SET_DBFLAG: + return "NETLOGON_CONTROL_SET_DBFLAG"; + case NETLOGON_CONTROL_BREAKPOINT: + return "NETLOGON_CONTROL_BREAKPOINT"; + default: + return talloc_asprintf(mem_ctx, "unknown function code: %d", + function_code); + } +} + + +/* + try a netlogon LogonControl +*/ +static bool test_LogonControl(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) + +{ + NTSTATUS status; + struct netr_LogonControl r; + union netr_CONTROL_QUERY_INFORMATION query; + int i,f; + enum netr_SchannelType secure_channel_type = SEC_CHAN_NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t function_codes[] = { + NETLOGON_CONTROL_QUERY, + NETLOGON_CONTROL_REPLICATE, + NETLOGON_CONTROL_SYNCHRONIZE, + NETLOGON_CONTROL_PDC_REPLICATE, + NETLOGON_CONTROL_REDISCOVER, + NETLOGON_CONTROL_TC_QUERY, + NETLOGON_CONTROL_TRANSPORT_NOTIFY, + NETLOGON_CONTROL_FIND_USER, + NETLOGON_CONTROL_CHANGE_PASSWORD, + NETLOGON_CONTROL_TC_VERIFY, + NETLOGON_CONTROL_FORCE_DNS_REG, + NETLOGON_CONTROL_QUERY_DNS_REG, + NETLOGON_CONTROL_BACKUP_CHANGE_LOG, + NETLOGON_CONTROL_TRUNCATE_LOG, + NETLOGON_CONTROL_SET_DBFLAG, + NETLOGON_CONTROL_BREAKPOINT + }; + + if (machine_credentials) { + secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + } + + torture_comment(tctx, "Testing LogonControl with secure channel type: %d\n", + secure_channel_type); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.function_code = 1; + r.out.query = &query; + + for (f=0;f<ARRAY_SIZE(function_codes); f++) { + for (i=1;i<5;i++) { + + r.in.function_code = function_codes[f]; + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl"); + + switch (r.in.level) { + case 1: + switch (r.in.function_code) { + case NETLOGON_CONTROL_REPLICATE: + case NETLOGON_CONTROL_SYNCHRONIZE: + case NETLOGON_CONTROL_PDC_REPLICATE: + case NETLOGON_CONTROL_BREAKPOINT: + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + if ((secure_channel_type == SEC_CHAN_BDC) || + (secure_channel_type == SEC_CHAN_WKSTA)) { + torture_assert_werr_equal(tctx, r.out.result, WERR_ACCESS_DENIED, + "LogonControl returned unexpected error code"); + } else { + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "LogonControl returned unexpected error code"); + } + break; + + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_TRANSPORT_NOTIFY: + case NETLOGON_CONTROL_FIND_USER: + case NETLOGON_CONTROL_CHANGE_PASSWORD: + case NETLOGON_CONTROL_TC_VERIFY: + case NETLOGON_CONTROL_FORCE_DNS_REG: + case NETLOGON_CONTROL_QUERY_DNS_REG: + case NETLOGON_CONTROL_SET_DBFLAG: + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "LogonControl returned unexpected error code"); + break; + case NETLOGON_CONTROL_TRUNCATE_LOG: + if ((secure_channel_type == SEC_CHAN_BDC) || + (secure_channel_type == SEC_CHAN_WKSTA)) { + torture_assert_werr_equal(tctx, r.out.result, WERR_ACCESS_DENIED, + "LogonControl returned unexpected error code"); + } else if (!W_ERROR_EQUAL(r.out.result, WERR_NOT_SUPPORTED)) { + torture_assert_werr_ok(tctx, r.out.result, + "LogonControl returned unexpected result"); + } + break; + default: + torture_assert_werr_ok(tctx, r.out.result, + "LogonControl returned unexpected result"); + break; + } + break; + case 2: + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "LogonControl returned unexpected error code"); + break; + default: + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, + "LogonControl returned unexpected error code"); + break; + } + } + } + + r.in.level = 52; + torture_comment(tctx, "Testing LogonControl function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + status = dcerpc_netr_LogonControl_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, "LogonControl"); + + return true; +} + + +/* + try a netlogon GetAnyDCName +*/ +static bool test_GetAnyDCName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_GetAnyDCName r; + const char *dcname = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.domainname = lpcfg_workgroup(tctx->lp_ctx); + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.out.dcname = &dcname; + + status = dcerpc_netr_GetAnyDCName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetAnyDCName"); + if ((!W_ERROR_IS_OK(r.out.result)) && + (!W_ERROR_EQUAL(r.out.result, WERR_NO_SUCH_DOMAIN))) { + return false; + } + + if (dcname) { + torture_comment(tctx, "\tDC is at '%s'\n", dcname); + } + + r.in.domainname = NULL; + + status = dcerpc_netr_GetAnyDCName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetAnyDCName"); + if ((!W_ERROR_IS_OK(r.out.result)) && + (!W_ERROR_EQUAL(r.out.result, WERR_NO_SUCH_DOMAIN))) { + return false; + } + + r.in.domainname = ""; + + status = dcerpc_netr_GetAnyDCName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetAnyDCName"); + if ((!W_ERROR_IS_OK(r.out.result)) && + (!W_ERROR_EQUAL(r.out.result, WERR_NO_SUCH_DOMAIN))) { + return false; + } + + return true; +} + + +/* + try a netlogon LogonControl2 +*/ +static bool test_LogonControl2(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) + +{ + NTSTATUS status; + struct netr_LogonControl2 r; + union netr_CONTROL_DATA_INFORMATION data; + union netr_CONTROL_QUERY_INFORMATION query; + enum netr_SchannelType secure_channel_type = SEC_CHAN_NULL; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + if (machine_credentials) { + secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + } + + torture_comment(tctx, "Testing LogonControl2 with secure channel type: %d\n", + secure_channel_type); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + r.in.function_code = NETLOGON_CONTROL_REDISCOVER; + r.in.data = &data; + r.out.query = &query; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + } + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + r.in.function_code = NETLOGON_CONTROL_TC_QUERY; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + } + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + r.in.function_code = NETLOGON_CONTROL_TRANSPORT_NOTIFY; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + } + + data.debug_level = ~0; + + r.in.function_code = NETLOGON_CONTROL_SET_DBFLAG; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + } + + ZERO_STRUCT(data); + r.in.function_code = 52; + r.in.data = &data; + + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + switch (secure_channel_type) { + case SEC_CHAN_NULL: + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, "LogonControl2"); + break; + default: + torture_assert_werr_equal(tctx, r.out.result, WERR_ACCESS_DENIED, "LogonControl2"); + break; + } + data.debug_level = ~0; + + r.in.function_code = NETLOGON_CONTROL_SET_DBFLAG; + r.in.data = &data; + + r.in.level = 52; + torture_comment(tctx, "Testing LogonControl2 function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, "LogonControl2"); + + return true; +} + +/* + try a netlogon DatabaseSync2 +*/ +static bool test_DatabaseSync2(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct netr_DatabaseSync2 r; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + struct netr_Authenticator return_authenticator, credential; + + struct netlogon_creds_CredentialState *creds; + const uint32_t database_ids[] = {0, 1, 2}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_SetupCredentials2(p, tctx, NETLOGON_NEG_AUTH2_FLAGS, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) { + return false; + } + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.preferredmaximumlength = (uint32_t)-1; + r.in.return_authenticator = &return_authenticator; + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + + uint32_t sync_context = 0; + + r.in.database_id = database_ids[i]; + r.in.sync_context = &sync_context; + r.out.sync_context = &sync_context; + r.in.restart_state = 0; + + torture_comment(tctx, "Testing DatabaseSync2 of id %d\n", r.in.database_id); + + do { + netlogon_creds_client_authenticator(creds, &credential); + + r.in.credential = &credential; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_DatabaseSync2_r(b, tctx, &r), + "DatabaseSync2 failed"); + if (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) + break; + + /* Native mode servers don't do this */ + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_SUPPORTED)) { + return true; + } + + torture_assert_ntstatus_ok(tctx, r.out.result, "DatabaseSync2"); + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + + +/* + try a netlogon LogonControl2Ex +*/ +static bool test_LogonControl2Ex(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) + +{ + NTSTATUS status; + struct netr_LogonControl2Ex r; + union netr_CONTROL_DATA_INFORMATION data; + union netr_CONTROL_QUERY_INFORMATION query; + enum netr_SchannelType secure_channel_type = SEC_CHAN_NULL; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + if (machine_credentials) { + secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + } + + torture_comment(tctx, "Testing LogonControl2Ex with secure channel type: %d\n", + secure_channel_type); + + r.in.logon_server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + r.in.function_code = NETLOGON_CONTROL_REDISCOVER; + r.in.data = &data; + r.out.query = &query; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + } + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + r.in.function_code = NETLOGON_CONTROL_TC_QUERY; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + } + + data.domain = lpcfg_workgroup(tctx->lp_ctx); + + r.in.function_code = NETLOGON_CONTROL_TRANSPORT_NOTIFY; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + } + + data.debug_level = ~0; + + r.in.function_code = NETLOGON_CONTROL_SET_DBFLAG; + r.in.data = &data; + + for (i=1;i<4;i++) { + r.in.level = i; + + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + } + + ZERO_STRUCT(data); + r.in.function_code = 52; + r.in.data = &data; + + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + switch (secure_channel_type) { + case SEC_CHAN_NULL: + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, "LogonControl2Ex"); + break; + default: + torture_assert_werr_equal(tctx, r.out.result, WERR_ACCESS_DENIED, "LogonControl2Ex"); + break; + } + data.debug_level = ~0; + + r.in.function_code = NETLOGON_CONTROL_SET_DBFLAG; + r.in.data = &data; + + r.in.level = 52; + torture_comment(tctx, "Testing LogonControl2Ex function code %s (%d) level %d\n", + function_code_str(tctx, r.in.function_code), r.in.function_code, r.in.level); + + status = dcerpc_netr_LogonControl2Ex_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonControl2Ex"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, "LogonControl2Ex"); + + return true; +} + +static bool test_netr_GetForestTrustInformation(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_GetForestTrustInformation r; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator a; + struct netr_Authenticator return_authenticator; + struct lsa_ForestTrustInformation *forest_trust_info; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + if (!test_SetupCredentials3(p1, tctx, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + machine_credentials, &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + netlogon_creds_client_authenticator(creds, &a); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &a; + r.in.flags = 0; + r.out.return_authenticator = &return_authenticator; + r.out.forest_trust_info = &forest_trust_info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_GetForestTrustInformation_r(b, tctx, &r), + "netr_GetForestTrustInformation failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_IMPLEMENTED)) { + torture_comment(tctx, "not considering NT_STATUS_NOT_IMPLEMENTED as an error\n"); + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, + "netr_GetForestTrustInformation failed"); + } + + torture_assert(tctx, + netlogon_creds_client_check(creds, &return_authenticator.cred), + "Credential chaining failed"); + + return true; +} + +static bool test_netr_DsRGetForestTrustInformation(struct torture_context *tctx, + struct dcerpc_pipe *p, const char *trusted_domain_name) +{ + NTSTATUS status; + struct netr_DsRGetForestTrustInformation r; + struct lsa_ForestTrustInformation info, *info_ptr; + struct dcerpc_binding_handle *b = p->binding_handle; + + info_ptr = &info; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.trusted_domain_name = trusted_domain_name; + r.in.flags = 0; + r.out.forest_trust_info = &info_ptr; + + torture_comment(tctx ,"Testing netr_DsRGetForestTrustInformation\n"); + + status = dcerpc_netr_DsRGetForestTrustInformation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "DsRGetForestTrustInformation"); + torture_assert_werr_ok(tctx, r.out.result, "DsRGetForestTrustInformation"); + + return true; +} + +/* + try a netlogon netr_DsrEnumerateDomainTrusts +*/ +static bool test_DsrEnumerateDomainTrusts(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_DsrEnumerateDomainTrusts r; + struct netr_DomainTrustList trusts; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.trust_flags = 0x3f; + r.out.trusts = &trusts; + + status = dcerpc_netr_DsrEnumerateDomainTrusts_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "DsrEnumerateDomaintrusts"); + torture_assert_werr_ok(tctx, r.out.result, "DsrEnumerateDomaintrusts"); + + /* when trusted_domain_name is NULL, netr_DsRGetForestTrustInformation + * will show non-forest trusts and all UPN suffixes of the own forest + * as LSA_FOREST_TRUST_TOP_LEVEL_NAME types */ + + if (r.out.trusts->count) { + if (!test_netr_DsRGetForestTrustInformation(tctx, p, NULL)) { + return false; + } + } + + for (i=0; i<r.out.trusts->count; i++) { + + /* get info for transitive forest trusts */ + + if (r.out.trusts->array[i].trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + if (!test_netr_DsRGetForestTrustInformation(tctx, p, + r.out.trusts->array[i].dns_name)) { + return false; + } + } + } + + return true; +} + +static bool test_netr_NetrEnumerateTrustedDomains(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_NetrEnumerateTrustedDomains r; + struct netr_Blob trusted_domains_blob; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.out.trusted_domains_blob = &trusted_domains_blob; + + status = dcerpc_netr_NetrEnumerateTrustedDomains_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_NetrEnumerateTrustedDomains"); + torture_assert_ntstatus_ok(tctx, r.out.result, "NetrEnumerateTrustedDomains"); + + return true; +} + +static bool test_netr_NetrEnumerateTrustedDomainsEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_NetrEnumerateTrustedDomainsEx r; + struct netr_DomainTrustList dom_trust_list; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.out.dom_trust_list = &dom_trust_list; + + status = dcerpc_netr_NetrEnumerateTrustedDomainsEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_NetrEnumerateTrustedDomainsEx"); + torture_assert_werr_ok(tctx, r.out.result, "NetrEnumerateTrustedDomainsEx"); + + return true; +} + + +static bool test_netr_DsRGetSiteName(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *computer_name, + const char *expected_site) +{ + NTSTATUS status; + struct netr_DsRGetSiteName r; + const char *site = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.computer_name = computer_name; + r.out.site = &site; + torture_comment(tctx, "Testing netr_DsRGetSiteName\n"); + + status = dcerpc_netr_DsRGetSiteName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "DsRGetSiteName"); + torture_assert_werr_ok(tctx, r.out.result, "DsRGetSiteName"); + torture_assert_str_equal(tctx, expected_site, site, "netr_DsRGetSiteName"); + + return true; +} + +/* + try a netlogon netr_DsRGetDCName +*/ +static bool test_netr_DsRGetDCName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_DsRGetDCName r; + struct netr_DsRGetDCNameInfo *info = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.domain_name = lpcfg_dnsdomain(tctx->lp_ctx); + r.in.domain_guid = NULL; + r.in.site_guid = NULL; + r.in.flags = DS_RETURN_DNS_NAME; + r.out.info = &info; + + status = dcerpc_netr_DsRGetDCName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "DsRGetDCName"); + torture_assert_werr_ok(tctx, r.out.result, "DsRGetDCName"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), + DS_DNS_CONTROLLER, + "DsRGetDCName"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), + DS_DNS_DOMAIN, + "DsRGetDCName"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCName"); + + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + r.in.flags = 0; + + status = dcerpc_netr_DsRGetDCName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "DsRGetDCName"); + torture_assert_werr_ok(tctx, r.out.result, "DsRGetDCName"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), 0, + "DsRGetDCName"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), 0, + "DsRGetDCName"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCName"); + + if (strcasecmp(info->dc_site_name, info->client_site_name) == 0) { + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_SERVER_CLOSEST)), + DS_SERVER_CLOSEST, + "DsRGetDCName"); + } + + return test_netr_DsRGetSiteName(p, tctx, + info->dc_unc, + info->dc_site_name); +} + +/* + try a netlogon netr_DsRGetDCNameEx +*/ +static bool test_netr_DsRGetDCNameEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_DsRGetDCNameEx r; + struct netr_DsRGetDCNameInfo *info = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.domain_name = lpcfg_dnsdomain(tctx->lp_ctx); + r.in.domain_guid = NULL; + r.in.site_name = NULL; + r.in.flags = DS_RETURN_DNS_NAME; + r.out.info = &info; + + status = dcerpc_netr_DsRGetDCNameEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), + DS_DNS_CONTROLLER, + "DsRGetDCNameEx"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), + DS_DNS_DOMAIN, + "DsRGetDCNameEx"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCNameEx"); + + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + r.in.flags = 0; + + status = dcerpc_netr_DsRGetDCNameEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), 0, + "DsRGetDCNameEx"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), 0, + "DsRGetDCNameEx"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCNameEx"); + + if (strcasecmp(info->dc_site_name, info->client_site_name) == 0) { + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_SERVER_CLOSEST)), + DS_SERVER_CLOSEST, + "DsRGetDCNameEx"); + } + + return test_netr_DsRGetSiteName(p, tctx, info->dc_unc, + info->dc_site_name); +} + +/* + try a netlogon netr_DsRGetDCNameEx2 +*/ +static bool test_netr_DsRGetDCNameEx2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct netr_DsRGetDCNameEx2 r; + struct netr_DsRGetDCNameInfo *info = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing netr_DsRGetDCNameEx2 with no inputs\n"); + ZERO_STRUCT(r.in); + r.in.flags = DS_RETURN_DNS_NAME; + r.out.info = &info; + + status = dcerpc_netr_DsRGetDCNameEx2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx2"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx2"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), + DS_DNS_CONTROLLER, + "DsRGetDCNameEx2"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), + DS_DNS_DOMAIN, + "DsRGetDCNameEx2"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCNameEx2"); + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.client_account = NULL; + r.in.mask = 0x00000000; + r.in.domain_name = lpcfg_dnsdomain(tctx->lp_ctx); + r.in.domain_guid = NULL; + r.in.site_name = NULL; + r.in.flags = DS_RETURN_DNS_NAME; + r.out.info = &info; + + torture_comment(tctx, "Testing netr_DsRGetDCNameEx2 without client account\n"); + + status = dcerpc_netr_DsRGetDCNameEx2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx2"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx2"); + + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + r.in.flags = 0; + + status = dcerpc_netr_DsRGetDCNameEx2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx2"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx2"); + + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_CONTROLLER)), 0, + "DsRGetDCNameEx2"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_DOMAIN)), 0, + "DsRGetDCNameEx2"); + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_DNS_FOREST_ROOT)), + DS_DNS_FOREST_ROOT, + "DsRGetDCNameEx2"); + + if (strcasecmp(info->dc_site_name, info->client_site_name) == 0) { + torture_assert_int_equal(tctx, + (info->dc_flags & (DS_SERVER_CLOSEST)), + DS_SERVER_CLOSEST, + "DsRGetDCNameEx2"); + } + + torture_comment(tctx, "Testing netr_DsRGetDCNameEx2 with client account\n"); + r.in.client_account = TEST_MACHINE_NAME"$"; + r.in.mask = ACB_SVRTRUST; + r.in.flags = DS_RETURN_FLAT_NAME; + r.out.info = &info; + + status = dcerpc_netr_DsRGetDCNameEx2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "netr_DsRGetDCNameEx2"); + torture_assert_werr_ok(tctx, r.out.result, "netr_DsRGetDCNameEx2"); + + return test_netr_DsRGetSiteName(p, tctx, info->dc_unc, + info->dc_site_name); +} + +/* This is a substitution for "samdb_server_site_name" which relies on the + * correct "lp_ctx" and therefore can't be used here. */ +static const char *server_site_name(struct torture_context *tctx, + struct ldb_context *ldb) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn, *server_dn; + const struct ldb_val *site_name_val; + const char *server_dn_str, *site_name; + + tmp_ctx = talloc_new(ldb); + if (tmp_ctx == NULL) { + goto failed; + } + + dn = ldb_dn_new(tmp_ctx, ldb, ""); + if (dn == NULL) { + goto failed; + } + + server_dn_str = samdb_search_string(ldb, tmp_ctx, dn, "serverName", + NULL); + if (server_dn_str == NULL) { + goto failed; + } + + server_dn = ldb_dn_new(tmp_ctx, ldb, server_dn_str); + if (server_dn == NULL) { + goto failed; + } + + /* CN=<Server name>, CN=Servers, CN=<Site name>, CN=Sites, ... */ + site_name_val = ldb_dn_get_component_val(server_dn, 2); + if (site_name_val == NULL) { + goto failed; + } + + site_name = (const char *) site_name_val->data; + + talloc_steal(tctx, site_name); + talloc_free(tmp_ctx); + + return site_name; + +failed: + talloc_free(tmp_ctx); + return NULL; +} + +static bool test_netr_DsrGetDcSiteCoverageW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + char *url; + struct ldb_context *sam_ctx = NULL; + NTSTATUS status; + struct netr_DsrGetDcSiteCoverageW r; + struct DcSitesCtr *ctr = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "This does only pass with the default site\n"); + + /* We won't double-check this when we are over 'local' transports */ + if (dcerpc_server_name(p)) { + /* Set up connection to SAMDB on DC */ + url = talloc_asprintf(tctx, "ldap://%s", dcerpc_server_name(p)); + sam_ctx = ldb_wrap_connect(tctx, tctx->ev, tctx->lp_ctx, url, + NULL, + samba_cmdline_get_creds(), + 0); + + torture_assert(tctx, sam_ctx, "Connection to the SAMDB on DC failed!"); + } + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.out.ctr = &ctr; + + status = dcerpc_netr_DsrGetDcSiteCoverageW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + torture_assert(tctx, ctr->num_sites == 1, + "we should per default only get the default site"); + if (sam_ctx != NULL) { + torture_assert_casestr_equal(tctx, ctr->sites[0].string, + server_site_name(tctx, sam_ctx), + "didn't return default site"); + } + + return true; +} + +static bool test_netr_DsRAddressToSitenamesW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + char *url; + struct ldb_context *sam_ctx = NULL; + NTSTATUS status; + struct netr_DsRAddressToSitenamesW r; + struct netr_DsRAddress addrs[6]; + struct sockaddr_in *addr; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *addr6; +#endif + struct netr_DsRAddressToSitenamesWCtr *ctr; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t i; + int ret; + + torture_comment(tctx, "This does only pass with the default site\n"); + + /* We won't double-check this when we are over 'local' transports */ + if (dcerpc_server_name(p)) { + /* Set up connection to SAMDB on DC */ + url = talloc_asprintf(tctx, "ldap://%s", dcerpc_server_name(p)); + sam_ctx = ldb_wrap_connect(tctx, tctx->ev, tctx->lp_ctx, url, + NULL, + samba_cmdline_get_creds(), + 0); + + torture_assert(tctx, sam_ctx, "Connection to the SAMDB on DC failed!"); + } + + /* First try valid IP addresses */ + + addrs[0].size = sizeof(struct sockaddr_in); + addrs[0].buffer = talloc_zero_array(tctx, uint8_t, addrs[0].size); + addr = (struct sockaddr_in *) addrs[0].buffer; + addrs[0].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "127.0.0.1", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[1].size = sizeof(struct sockaddr_in); + addrs[1].buffer = talloc_zero_array(tctx, uint8_t, addrs[1].size); + addr = (struct sockaddr_in *) addrs[1].buffer; + addrs[1].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "0.0.0.0", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[2].size = sizeof(struct sockaddr_in); + addrs[2].buffer = talloc_zero_array(tctx, uint8_t, addrs[2].size); + addr = (struct sockaddr_in *) addrs[2].buffer; + addrs[2].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "255.255.255.255", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + +#ifdef HAVE_IPV6 + addrs[3].size = sizeof(struct sockaddr_in6); + addrs[3].buffer = talloc_zero_array(tctx, uint8_t, addrs[3].size); + addr6 = (struct sockaddr_in6 *) addrs[3].buffer; + addrs[3].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "::1", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[4].size = sizeof(struct sockaddr_in6); + addrs[4].buffer = talloc_zero_array(tctx, uint8_t, addrs[4].size); + addr6 = (struct sockaddr_in6 *) addrs[4].buffer; + addrs[4].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "::", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[5].size = sizeof(struct sockaddr_in6); + addrs[5].buffer = talloc_zero_array(tctx, uint8_t, addrs[5].size); + addr6 = (struct sockaddr_in6 *) addrs[5].buffer; + addrs[5].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "ff02::1", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); +#else + /* the test cases are repeated to have exactly 6. This is for + * compatibility with IPv4-only machines */ + addrs[3].size = sizeof(struct sockaddr_in); + addrs[3].buffer = talloc_zero_array(tctx, uint8_t, addrs[3].size); + addr = (struct sockaddr_in *) addrs[3].buffer; + addrs[3].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "127.0.0.1", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[4].size = sizeof(struct sockaddr_in); + addrs[4].buffer = talloc_zero_array(tctx, uint8_t, addrs[4].size); + addr = (struct sockaddr_in *) addrs[4].buffer; + addrs[4].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "0.0.0.0", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[5].size = sizeof(struct sockaddr_in); + addrs[5].buffer = talloc_zero_array(tctx, uint8_t, addrs[5].size); + addr = (struct sockaddr_in *) addrs[5].buffer; + addrs[5].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "255.255.255.255", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); +#endif + + ctr = talloc(tctx, struct netr_DsRAddressToSitenamesWCtr); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.count = 6; + r.in.addresses = addrs; + r.out.ctr = &ctr; + + status = dcerpc_netr_DsRAddressToSitenamesW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + if (sam_ctx != NULL) { + for (i = 0; i < 3; i++) { + torture_assert_casestr_equal(tctx, + ctr->sitename[i].string, + server_site_name(tctx, sam_ctx), + "didn't return default site"); + } + for (i = 3; i < 6; i++) { + /* Windows returns "NULL" for the sitename if it isn't + * IPv6 configured */ + if (torture_setting_bool(tctx, "samba4", false)) { + torture_assert_casestr_equal(tctx, + ctr->sitename[i].string, + server_site_name(tctx, sam_ctx), + "didn't return default site"); + } + } + } + + /* Now try invalid ones (too short buffers) */ + + addrs[0].size = 0; + addrs[1].size = 1; + addrs[2].size = 4; + + addrs[3].size = 0; + addrs[4].size = 1; + addrs[5].size = 4; + + status = dcerpc_netr_DsRAddressToSitenamesW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + for (i = 0; i < 6; i++) { + torture_assert(tctx, ctr->sitename[i].string == NULL, + "sitename should be null"); + } + + /* Now try invalid ones (wrong address types) */ + + addrs[0].size = 10; + addrs[0].buffer[0] = AF_UNSPEC; + addrs[1].size = 10; + addrs[1].buffer[0] = AF_UNIX; /* AF_LOCAL = AF_UNIX */ + addrs[2].size = 10; + addrs[2].buffer[0] = AF_UNIX; + + addrs[3].size = 10; + addrs[3].buffer[0] = 250; + addrs[4].size = 10; + addrs[4].buffer[0] = 251; + addrs[5].size = 10; + addrs[5].buffer[0] = 252; + + status = dcerpc_netr_DsRAddressToSitenamesW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + for (i = 0; i < 6; i++) { + torture_assert(tctx, ctr->sitename[i].string == NULL, + "sitename should be null"); + } + + return true; +} + +static bool test_netr_DsRAddressToSitenamesExW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + char *url; + struct ldb_context *sam_ctx = NULL; + NTSTATUS status; + struct netr_DsRAddressToSitenamesExW r; + struct netr_DsRAddress addrs[6]; + struct sockaddr_in *addr; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *addr6; +#endif + struct netr_DsRAddressToSitenamesExWCtr *ctr; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t i; + int ret; + + torture_comment(tctx, "This does pass with the default site\n"); + + /* We won't double-check this when we are over 'local' transports */ + if (dcerpc_server_name(p)) { + /* Set up connection to SAMDB on DC */ + url = talloc_asprintf(tctx, "ldap://%s", dcerpc_server_name(p)); + sam_ctx = ldb_wrap_connect(tctx, tctx->ev, tctx->lp_ctx, url, + NULL, + samba_cmdline_get_creds(), + 0); + + torture_assert(tctx, sam_ctx, "Connection to the SAMDB on DC failed!"); + } + + /* First try valid IP addresses */ + + addrs[0].size = sizeof(struct sockaddr_in); + addrs[0].buffer = talloc_zero_array(tctx, uint8_t, addrs[0].size); + addr = (struct sockaddr_in *) addrs[0].buffer; + addrs[0].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "127.0.0.1", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[1].size = sizeof(struct sockaddr_in); + addrs[1].buffer = talloc_zero_array(tctx, uint8_t, addrs[1].size); + addr = (struct sockaddr_in *) addrs[1].buffer; + addrs[1].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "0.0.0.0", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[2].size = sizeof(struct sockaddr_in); + addrs[2].buffer = talloc_zero_array(tctx, uint8_t, addrs[2].size); + addr = (struct sockaddr_in *) addrs[2].buffer; + addrs[2].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "255.255.255.255", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + +#ifdef HAVE_IPV6 + addrs[3].size = sizeof(struct sockaddr_in6); + addrs[3].buffer = talloc_zero_array(tctx, uint8_t, addrs[3].size); + addr6 = (struct sockaddr_in6 *) addrs[3].buffer; + addrs[3].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "::1", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[4].size = sizeof(struct sockaddr_in6); + addrs[4].buffer = talloc_zero_array(tctx, uint8_t, addrs[4].size); + addr6 = (struct sockaddr_in6 *) addrs[4].buffer; + addrs[4].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "::", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[5].size = sizeof(struct sockaddr_in6); + addrs[5].buffer = talloc_zero_array(tctx, uint8_t, addrs[5].size); + addr6 = (struct sockaddr_in6 *) addrs[5].buffer; + addrs[5].buffer[0] = AF_INET6; + ret = inet_pton(AF_INET6, "ff02::1", &addr6->sin6_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); +#else + /* the test cases are repeated to have exactly 6. This is for + * compatibility with IPv4-only machines */ + addrs[3].size = sizeof(struct sockaddr_in); + addrs[3].buffer = talloc_zero_array(tctx, uint8_t, addrs[3].size); + addr = (struct sockaddr_in *) addrs[3].buffer; + addrs[3].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "127.0.0.1", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[4].size = sizeof(struct sockaddr_in); + addrs[4].buffer = talloc_zero_array(tctx, uint8_t, addrs[4].size); + addr = (struct sockaddr_in *) addrs[4].buffer; + addrs[4].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "0.0.0.0", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); + + addrs[5].size = sizeof(struct sockaddr_in); + addrs[5].buffer = talloc_zero_array(tctx, uint8_t, addrs[5].size); + addr = (struct sockaddr_in *) addrs[5].buffer; + addrs[5].buffer[0] = AF_INET; + ret = inet_pton(AF_INET, "255.255.255.255", &addr->sin_addr); + torture_assert(tctx, ret > 0, "inet_pton failed"); +#endif + + ctr = talloc(tctx, struct netr_DsRAddressToSitenamesExWCtr); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.count = 6; + r.in.addresses = addrs; + r.out.ctr = &ctr; + + status = dcerpc_netr_DsRAddressToSitenamesExW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + if (sam_ctx != NULL) { + for (i = 0; i < 3; i++) { + torture_assert_casestr_equal(tctx, + ctr->sitename[i].string, + server_site_name(tctx, sam_ctx), + "didn't return default site"); + torture_assert(tctx, ctr->subnetname[i].string == NULL, + "subnet should be null"); + } + for (i = 3; i < 6; i++) { + /* Windows returns "NULL" for the sitename if it isn't + * IPv6 configured */ + if (torture_setting_bool(tctx, "samba4", false)) { + torture_assert_casestr_equal(tctx, + ctr->sitename[i].string, + server_site_name(tctx, sam_ctx), + "didn't return default site"); + } + torture_assert(tctx, ctr->subnetname[i].string == NULL, + "subnet should be null"); + } + } + + /* Now try invalid ones (too short buffers) */ + + addrs[0].size = 0; + addrs[1].size = 1; + addrs[2].size = 4; + + addrs[3].size = 0; + addrs[4].size = 1; + addrs[5].size = 4; + + status = dcerpc_netr_DsRAddressToSitenamesExW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + for (i = 0; i < 6; i++) { + torture_assert(tctx, ctr->sitename[i].string == NULL, + "sitename should be null"); + torture_assert(tctx, ctr->subnetname[i].string == NULL, + "subnet should be null"); + } + + addrs[0].size = 10; + addrs[0].buffer[0] = AF_UNSPEC; + addrs[1].size = 10; + addrs[1].buffer[0] = AF_UNIX; /* AF_LOCAL = AF_UNIX */ + addrs[2].size = 10; + addrs[2].buffer[0] = AF_UNIX; + + addrs[3].size = 10; + addrs[3].buffer[0] = 250; + addrs[4].size = 10; + addrs[4].buffer[0] = 251; + addrs[5].size = 10; + addrs[5].buffer[0] = 252; + + status = dcerpc_netr_DsRAddressToSitenamesExW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "failed"); + torture_assert_werr_ok(tctx, r.out.result, "failed"); + + for (i = 0; i < 6; i++) { + torture_assert(tctx, ctr->sitename[i].string == NULL, + "sitename should be null"); + torture_assert(tctx, ctr->subnetname[i].string == NULL, + "subnet should be null"); + } + + return true; +} + +static bool test_netr_ServerGetTrustInfo_flags(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials, + uint32_t negotiate_flags) +{ + struct netr_ServerGetTrustInfo r; + + struct netr_Authenticator a; + struct netr_Authenticator return_authenticator; + struct samr_Password new_owf_password; + struct samr_Password old_owf_password; + struct netr_TrustInfo *trust_info; + + struct netlogon_creds_CredentialState *creds; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + struct samr_Password nt_hash; + + if (!test_SetupCredentials3(p1, tctx, negotiate_flags, + machine_credentials, &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + netlogon_creds_client_authenticator(creds, &a); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = cli_credentials_get_secure_channel_type(machine_credentials); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &a; + + r.out.return_authenticator = &return_authenticator; + r.out.new_owf_password = &new_owf_password; + r.out.old_owf_password = &old_owf_password; + r.out.trust_info = &trust_info; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerGetTrustInfo_r(b, tctx, &r), + "ServerGetTrustInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerGetTrustInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &return_authenticator.cred), "Credential chaining failed"); + + E_md4hash(cli_credentials_get_password(machine_credentials), nt_hash.hash); + + netlogon_creds_des_decrypt(creds, &new_owf_password); + + dump_data(1, new_owf_password.hash, 16); + dump_data(1, nt_hash.hash, 16); + + torture_assert_mem_equal(tctx, new_owf_password.hash, nt_hash.hash, 16, + "received unexpected owf password\n"); + + return true; +} + +static bool test_netr_ServerGetTrustInfo(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + return test_netr_ServerGetTrustInfo_flags(tctx, p, machine_credentials, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_netr_ServerGetTrustInfo_AES(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + return test_netr_ServerGetTrustInfo_flags(tctx, p, machine_credentials, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +static bool test_GetDomainInfo(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + struct netr_LogonGetDomainInfo r; + struct netr_WorkstationInformation q1; + struct netr_Authenticator a; + struct netlogon_creds_CredentialState *creds; + struct netr_OsVersion os; + union netr_WorkstationInfo query; + union netr_DomainInfo info; + const char* const attrs[] = { "dNSHostName", "operatingSystem", + "operatingSystemServicePack", "operatingSystemVersion", + "servicePrincipalName", NULL }; + char *url; + struct ldb_context *sam_ctx = NULL; + struct ldb_message **res; + struct ldb_message_element *spn_el; + int ret, i; + char *version_str; + const char *old_dnsname = NULL; + char **spns = NULL; + int num_spns = 0; + char *temp_str = NULL; + char *temp_str2 = NULL; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + struct netr_OneDomainInfo *odi1 = NULL; + struct netr_OneDomainInfo *odi2 = NULL; + struct netr_trust_extension_info *tex2 = NULL; + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo\n"); + + if (!test_SetupCredentials3(p1, tctx, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + machine_credentials, &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + /* We won't double-check this when we are over 'local' transports */ + if (dcerpc_server_name(p)) { + /* Set up connection to SAMDB on DC */ + url = talloc_asprintf(tctx, "ldap://%s", dcerpc_server_name(p)); + sam_ctx = ldb_wrap_connect(tctx, tctx->ev, tctx->lp_ctx, url, + NULL, + samba_cmdline_get_creds(), + 0); + + torture_assert(tctx, sam_ctx, "Connection to the SAMDB on DC failed!"); + } + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 1st call (no variation of DNS hostname)\n"); + netlogon_creds_client_authenticator(creds, &a); + + ZERO_STRUCT(r); + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &a; + r.in.level = 1; + r.in.return_authenticator = &a; + r.in.query = &query; + r.out.return_authenticator = &a; + r.out.info = &info; + + ZERO_STRUCT(os); + os.os.MajorVersion = 123; + os.os.MinorVersion = 456; + os.os.BuildNumber = 789; + os.os.CSDVersion = "Service Pack 10"; + os.os.ServicePackMajor = 10; + os.os.ServicePackMinor = 1; + os.os.SuiteMask = NETR_VER_SUITE_SINGLEUSERTS; + os.os.ProductType = NETR_VER_NT_SERVER; + os.os.Reserved = 0; + + version_str = talloc_asprintf(tctx, "%d.%d (%d)", os.os.MajorVersion, + os.os.MinorVersion, os.os.BuildNumber); + + ZERO_STRUCT(q1); + q1.dns_hostname = talloc_asprintf(tctx, "%s.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + q1.sitename = "Default-First-Site-Name"; + q1.os_version.os = &os; + q1.os_name.string = talloc_asprintf(tctx, + "Tortured by Samba4 RPC-NETLOGON: %s", + timestring(tctx, time(NULL))); + + /* The workstation handles the "servicePrincipalName" and DNS hostname + updates */ + q1.workstation_flags = NETR_WS_FLAG_HANDLES_SPN_UPDATE; + + query.workstation_info = &q1; + + if (sam_ctx) { + /* Gets back the old DNS hostname in AD */ + ret = gendb_search(sam_ctx, tctx, NULL, &res, attrs, + "(sAMAccountName=%s$)", TEST_MACHINE_NAME); + old_dnsname = + ldb_msg_find_attr_as_string(res[0], "dNSHostName", NULL); + + /* Gets back the "servicePrincipalName"s in AD */ + spn_el = ldb_msg_find_element(res[0], "servicePrincipalName"); + if (spn_el != NULL) { + for (i=0; i < spn_el->num_values; i++) { + spns = talloc_realloc(tctx, spns, char *, i + 1); + spns[i] = (char *) spn_el->values[i].data; + } + num_spns = i; + } + } + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + smb_msleep(250); + + if (sam_ctx) { + /* AD workstation infos entry check */ + ret = gendb_search(sam_ctx, tctx, NULL, &res, attrs, + "(sAMAccountName=%s$)", TEST_MACHINE_NAME); + torture_assert(tctx, ret == 1, "Test machine account not found in SAMDB on DC! Has the workstation been joined?"); + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystem", NULL), + q1.os_name.string, "'operatingSystem' wrong!"); + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemServicePack", NULL), + os.os.CSDVersion, "'operatingSystemServicePack' wrong!"); + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemVersion", NULL), + version_str, "'operatingSystemVersion' wrong!"); + + if (old_dnsname != NULL) { + /* If before a DNS hostname was set then it should remain + the same in combination with the "servicePrincipalName"s. + The DNS hostname should also be returned by our + "LogonGetDomainInfo" call (in the domain info structure). */ + + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "dNSHostName", NULL), + old_dnsname, "'DNS hostname' was not set!"); + + spn_el = ldb_msg_find_element(res[0], "servicePrincipalName"); + torture_assert(tctx, ((spns != NULL) && (spn_el != NULL)), + "'servicePrincipalName's not set!"); + torture_assert(tctx, spn_el->num_values == num_spns, + "'servicePrincipalName's incorrect!"); + for (i=0; (i < spn_el->num_values) && (i < num_spns); i++) + torture_assert_str_equal(tctx, + (char *) spn_el->values[i].data, + spns[i], "'servicePrincipalName's incorrect!"); + + torture_assert_str_equal(tctx, + info.domain_info->dns_hostname.string, + old_dnsname, + "Out 'DNS hostname' doesn't match the old one!"); + } else { + /* If no DNS hostname was set then also now none should be set, + the "servicePrincipalName"s should remain empty and no DNS + hostname should be returned by our "LogonGetDomainInfo" + call (in the domain info structure). */ + + torture_assert(tctx, + ldb_msg_find_attr_as_string(res[0], "dNSHostName", NULL) == NULL, + "'DNS hostname' was set!"); + + spn_el = ldb_msg_find_element(res[0], "servicePrincipalName"); + torture_assert(tctx, ((spns == NULL) && (spn_el == NULL)), + "'servicePrincipalName's were set!"); + + torture_assert(tctx, + info.domain_info->dns_hostname.string == NULL, + "Out 'DNS host name' was set!"); + } + } + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags + == NETR_WS_FLAG_HANDLES_SPN_UPDATE, + "Out 'workstation flags' don't match!"); + + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 2nd call (variation of DNS hostname doesn't work)\n"); + netlogon_creds_client_authenticator(creds, &a); + + /* Wipe out the CSDVersion, and prove which values still 'stick' */ + os.os.CSDVersion = ""; + + /* Change also the DNS hostname to test differences in behaviour */ + talloc_free(discard_const_p(char, q1.dns_hostname)); + q1.dns_hostname = talloc_asprintf(tctx, "%s2.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + + /* The workstation handles the "servicePrincipalName" and DNS hostname + updates */ + q1.workstation_flags = NETR_WS_FLAG_HANDLES_SPN_UPDATE; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + smb_msleep(250); + + if (sam_ctx) { + /* AD workstation infos entry check */ + ret = gendb_search(sam_ctx, tctx, NULL, &res, attrs, + "(sAMAccountName=%s$)", TEST_MACHINE_NAME); + torture_assert(tctx, ret == 1, "Test machine account not found in SAMDB on DC! Has the workstation been joined?"); + + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystem", NULL), + q1.os_name.string, "'operatingSystem' should stick!"); + torture_assert(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemServicePack", NULL) == NULL, + "'operatingSystemServicePack' shouldn't stick!"); + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemVersion", NULL), + version_str, "'operatingSystemVersion' wrong!"); + + /* The DNS host name shouldn't have been updated by the server */ + + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "dNSHostName", NULL), + old_dnsname, "'DNS host name' did change!"); + + /* Find the two "servicePrincipalName"s which the DC shouldn't have been + updated (HOST/<Netbios name> and HOST/<FQDN name>) - see MS-NRPC + 3.5.4.3.9 */ + spn_el = ldb_msg_find_element(res[0], "servicePrincipalName"); + torture_assert(tctx, spn_el != NULL, + "There should exist 'servicePrincipalName's in AD!"); + temp_str = talloc_asprintf(tctx, "HOST/%s", TEST_MACHINE_NAME); + for (i=0; i < spn_el->num_values; i++) + if (strcasecmp((char *) spn_el->values[i].data, temp_str) == 0) + break; + torture_assert(tctx, i != spn_el->num_values, + "'servicePrincipalName' HOST/<Netbios name> not found!"); + temp_str = talloc_asprintf(tctx, "HOST/%s", old_dnsname); + for (i=0; i < spn_el->num_values; i++) + if (strcasecmp((char *) spn_el->values[i].data, temp_str) == 0) + break; + torture_assert(tctx, i != spn_el->num_values, + "'servicePrincipalName' HOST/<FQDN name> not found!"); + + /* Check that the out DNS hostname was set properly */ + torture_assert_str_equal(tctx, info.domain_info->dns_hostname.string, + old_dnsname, "Out 'DNS hostname' doesn't match the old one!"); + } + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags == NETR_WS_FLAG_HANDLES_SPN_UPDATE, + "Out 'workstation flags' don't match!"); + + + /* Now try the same but the workstation flags set to 0 */ + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 3rd call (variation of DNS hostname doesn't work)\n"); + netlogon_creds_client_authenticator(creds, &a); + + /* Change also the DNS hostname to test differences in behaviour */ + talloc_free(discard_const_p(char, q1.dns_hostname)); + q1.dns_hostname = talloc_asprintf(tctx, "%s2.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + + /* Wipe out the osVersion, and prove which values still 'stick' */ + q1.os_version.os = NULL; + + /* Let the DC handle the "servicePrincipalName" and DNS hostname + updates */ + q1.workstation_flags = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + smb_msleep(250); + + if (sam_ctx) { + /* AD workstation infos entry check */ + ret = gendb_search(sam_ctx, tctx, NULL, &res, attrs, + "(sAMAccountName=%s$)", TEST_MACHINE_NAME); + torture_assert(tctx, ret == 1, "Test machine account not found in SAMDB on DC! Has the workstation been joined?"); + + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystem", NULL), + q1.os_name.string, "'operatingSystem' should stick!"); + torture_assert(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemServicePack", NULL) == NULL, + "'operatingSystemServicePack' shouldn't stick!"); + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "operatingSystemVersion", NULL), + version_str, "'operatingSystemVersion' wrong!"); + + /* The DNS host name shouldn't have been updated by the server */ + + torture_assert_str_equal(tctx, + ldb_msg_find_attr_as_string(res[0], "dNSHostName", NULL), + old_dnsname, "'DNS host name' did change!"); + + /* Find the two "servicePrincipalName"s which the DC shouldn't have been + updated (HOST/<Netbios name> and HOST/<FQDN name>) - see MS-NRPC + 3.5.4.3.9 */ + spn_el = ldb_msg_find_element(res[0], "servicePrincipalName"); + torture_assert(tctx, spn_el != NULL, + "There should exist 'servicePrincipalName's in AD!"); + temp_str = talloc_asprintf(tctx, "HOST/%s", TEST_MACHINE_NAME); + for (i=0; i < spn_el->num_values; i++) + if (strcasecmp((char *) spn_el->values[i].data, temp_str) == 0) + break; + torture_assert(tctx, i != spn_el->num_values, + "'servicePrincipalName' HOST/<Netbios name> not found!"); + temp_str = talloc_asprintf(tctx, "HOST/%s", old_dnsname); + for (i=0; i < spn_el->num_values; i++) + if (strcasecmp((char *) spn_el->values[i].data, temp_str) == 0) + break; + torture_assert(tctx, i != spn_el->num_values, + "'servicePrincipalName' HOST/<FQDN name> not found!"); + + /* Here the server gives us NULL as the out DNS hostname */ + torture_assert(tctx, info.domain_info->dns_hostname.string == NULL, + "Out 'DNS hostname' should be NULL!"); + } + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags == 0, + "Out 'workstation flags' don't match!"); + + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 4th call (verification of DNS hostname and check for trusted domains)\n"); + netlogon_creds_client_authenticator(creds, &a); + + /* Put the DNS hostname back */ + talloc_free(discard_const_p(char, q1.dns_hostname)); + q1.dns_hostname = talloc_asprintf(tctx, "%s.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + + /* The workstation handles the "servicePrincipalName" and DNS hostname + updates */ + q1.workstation_flags = NETR_WS_FLAG_HANDLES_SPN_UPDATE; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + smb_msleep(250); + + /* Now the in/out DNS hostnames should be the same */ + torture_assert_str_equal(tctx, + info.domain_info->dns_hostname.string, + query.workstation_info->dns_hostname, + "In/Out 'DNS hostnames' don't match!"); + old_dnsname = info.domain_info->dns_hostname.string; + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags + == NETR_WS_FLAG_HANDLES_SPN_UPDATE, + "Out 'workstation flags' don't match!"); + + /* Checks for trusted domains */ + torture_assert(tctx, + (info.domain_info->trusted_domain_count != 0) + && (info.domain_info->trusted_domains != NULL), + "Trusted domains have been requested!"); + + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 5th call (check for trusted domains)\n"); + netlogon_creds_client_authenticator(creds, &a); + + /* The workstation handles the "servicePrincipalName" and DNS hostname + updates and requests inbound trusts */ + q1.workstation_flags = NETR_WS_FLAG_HANDLES_SPN_UPDATE + | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + smb_msleep(250); + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags + == (NETR_WS_FLAG_HANDLES_SPN_UPDATE + | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS), + "Out 'workstation flags' don't match!"); + + /* Checks for trusted domains */ + torture_assert(tctx, + (info.domain_info->trusted_domain_count != 0) + && (info.domain_info->trusted_domains != NULL), + "Trusted domains have been requested!"); + + odi1 = &info.domain_info->primary_domain; + + torture_assert(tctx, !GUID_all_zero(&odi1->domain_guid), + "primary domain_guid needs to be valid"); + + for (i=0; i < info.domain_info->trusted_domain_count; i++) { + struct netr_OneDomainInfo *odiT = + &info.domain_info->trusted_domains[i]; + struct netr_trust_extension_info *texT = NULL; + + torture_assert_int_equal(tctx, odiT->trust_extension.length, 16, + "trust_list should have extension"); + torture_assert(tctx, odiT->trust_extension.info != NULL, + "trust_list should have extension"); + texT = &odiT->trust_extension.info->info; + + if (GUID_equal(&odiT->domain_guid, &odi1->domain_guid)) { + odi2 = odiT; + tex2 = texT; + continue; + } + + torture_assert_int_equal(tctx, + texT->flags & NETR_TRUST_FLAG_PRIMARY, + 0, + "trust_list flags should not have PRIMARY"); + + torture_assert(tctx, odiT->domainname.string != NULL, + "trust_list domainname should be valid"); + if (texT->trust_type == LSA_TRUST_TYPE_DOWNLEVEL || + texT->trust_type == LSA_TRUST_TYPE_MIT) + { + torture_assert(tctx, odiT->dns_domainname.string == NULL, + "trust_list dns_domainname should be NULL for downlevel or MIT"); + } else { + torture_assert(tctx, odiT->dns_domainname.string != NULL, + "trust_list dns_domainname should be valid for uplevel"); + } + torture_assert(tctx, odiT->dns_forestname.string == NULL, + "trust_list dns_forestname needs to be NULL"); + + torture_assert(tctx, odiT->domain_sid != NULL, + "trust_list domain_sid needs to be valid"); + } + + torture_assert(tctx, odi2 != NULL, + "trust_list primary domain not found."); + + torture_assert_str_equal(tctx, + odi1->domainname.string, + odi2->domainname.string, + "netbios name should match"); + + temp_str = talloc_strdup(tctx, odi1->dns_domainname.string); + torture_assert(tctx, temp_str != NULL, + "primary_domain dns_domainname copy"); + temp_str2 = strrchr(temp_str, '.'); + torture_assert(tctx, temp_str2 != NULL && temp_str2[1] == '\0', + "primary_domain dns_domainname needs trailing '.'"); + temp_str2[0] = '\0'; + torture_assert_str_equal(tctx, + temp_str, + odi2->dns_domainname.string, + "dns domainname should match " + "(without trailing '.')"); + + temp_str = talloc_strdup(tctx, odi1->dns_forestname.string); + torture_assert(tctx, temp_str != NULL, + "primary_domain dns_forestname copy"); + temp_str2 = strrchr(temp_str, '.'); + torture_assert(tctx, temp_str2 != NULL && temp_str2[1] == '\0', + "primary_domain dns_forestname needs trailing '.'"); + temp_str2[0] = '\0'; + torture_assert(tctx, odi2->dns_forestname.string == NULL, + "trust_list dns_forestname needs to be NULL"); + + torture_assert_guid_equal(tctx, odi1->domain_guid, odi2->domain_guid, + "domain_guid should match"); + torture_assert(tctx, odi1->domain_sid != NULL, + "primary domain_sid needs to be valid"); + torture_assert(tctx, odi2->domain_sid != NULL, + "trust_list domain_sid needs to be valid"); + torture_assert_sid_equal(tctx, odi1->domain_sid, odi2->domain_sid, + "domain_sid should match"); + + torture_assert_int_equal(tctx, odi1->trust_extension.length, 0, + "primary_domain should not have extension"); + torture_assert_int_equal(tctx, odi2->trust_extension.length, 16, + "trust_list should have extension"); + torture_assert(tctx, odi2->trust_extension.info != NULL, + "trust_list should have extension"); + tex2 = &odi2->trust_extension.info->info; + torture_assert_int_equal(tctx, + tex2->flags & NETR_TRUST_FLAG_PRIMARY, + NETR_TRUST_FLAG_PRIMARY, + "trust_list flags should have PRIMARY"); + torture_assert_int_equal(tctx, + tex2->flags & NETR_TRUST_FLAG_IN_FOREST, + NETR_TRUST_FLAG_IN_FOREST, + "trust_list flags should have IN_FOREST"); + torture_assert_int_equal(tctx, + tex2->flags & NETR_TRUST_FLAG_NATIVE, + NETR_TRUST_FLAG_NATIVE, + "trust_list flags should have NATIVE"); + torture_assert_int_equal(tctx, + tex2->flags & ~NETR_TRUST_FLAG_TREEROOT, + NETR_TRUST_FLAG_IN_FOREST | + NETR_TRUST_FLAG_PRIMARY | + NETR_TRUST_FLAG_NATIVE, + "trust_list flags IN_FOREST, PRIMARY, NATIVE " + "(TREEROOT optional)"); + if (strcmp(odi1->dns_domainname.string, odi1->dns_forestname.string) == 0) { + torture_assert_int_equal(tctx, + tex2->flags & NETR_TRUST_FLAG_TREEROOT, + NETR_TRUST_FLAG_TREEROOT, + "trust_list flags TREEROOT on forest root"); + torture_assert_int_equal(tctx, + tex2->parent_index, 0, + "trust_list no parent on foreset root"); + } + torture_assert_int_equal(tctx, + tex2->trust_type, LSA_TRUST_TYPE_UPLEVEL, + "trust_list uplevel"); + torture_assert_int_equal(tctx, + tex2->trust_attributes, 0, + "trust_list no attributes"); + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 6th call (no DNS hostname)\n"); + netlogon_creds_client_authenticator(creds, &a); + + query.workstation_info->dns_hostname = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + /* The old DNS hostname should stick */ + torture_assert_str_equal(tctx, + info.domain_info->dns_hostname.string, + old_dnsname, + "'DNS hostname' changed!"); + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 7th call (extra workstation flags)\n"); + netlogon_creds_client_authenticator(creds, &a); + + q1.workstation_flags = NETR_WS_FLAG_HANDLES_SPN_UPDATE + | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS | 0x4; + + /* Put the DNS hostname back */ + talloc_free(discard_const_p(char, q1.dns_hostname)); + q1.dns_hostname = talloc_asprintf(tctx, "%s.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + + /* Checks "workstation flags" */ + torture_assert(tctx, + info.domain_info->workstation_flags + == (NETR_WS_FLAG_HANDLES_SPN_UPDATE + | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS), + "Out 'workstation flags' don't match!"); + + if (!torture_setting_bool(tctx, "dangerous", false)) { + torture_comment(tctx, "Not testing netr_LogonGetDomainInfo 8th call (no workstation info) - enable dangerous tests in order to do so\n"); + } else { + /* Try a call without the workstation information structure */ + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo 8th call (no workstation info)\n"); + netlogon_creds_client_authenticator(creds, &a); + + query.workstation_info = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonGetDomainInfo_r(b, tctx, &r), + "LogonGetDomainInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonGetDomainInfo failed"); + torture_assert(tctx, netlogon_creds_client_check(creds, &a.cred), "Credential chaining failed"); + } + + return true; +} + +static bool test_GetDomainInfo_async(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *machine_credentials) +{ + NTSTATUS status; + struct netr_LogonGetDomainInfo r; + struct netr_WorkstationInformation q1; + struct netr_Authenticator a; +#define ASYNC_COUNT 100 + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState *creds_async[ASYNC_COUNT]; + struct tevent_req *req[ASYNC_COUNT]; + int i; + union netr_WorkstationInfo query; + union netr_DomainInfo info; + struct dcerpc_pipe *p = NULL; + + torture_comment(tctx, "Testing netr_LogonGetDomainInfo - async count %d\n", ASYNC_COUNT); + + if (!test_SetupCredentials3(p, tctx, NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES, + machine_credentials, &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, machine_credentials, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + + ZERO_STRUCT(r); + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &a; + r.in.level = 1; + r.in.return_authenticator = &a; + r.in.query = &query; + r.out.return_authenticator = &a; + r.out.info = &info; + + ZERO_STRUCT(q1); + q1.dns_hostname = talloc_asprintf(tctx, "%s.%s", TEST_MACHINE_NAME, + lpcfg_dnsdomain(tctx->lp_ctx)); + q1.sitename = "Default-First-Site-Name"; + q1.os_name.string = "UNIX/Linux or similar"; + + query.workstation_info = &q1; + + for (i=0;i<ASYNC_COUNT;i++) { + netlogon_creds_client_authenticator(creds, &a); + + creds_async[i] = (struct netlogon_creds_CredentialState *)talloc_memdup(creds, creds, sizeof(*creds)); + req[i] = dcerpc_netr_LogonGetDomainInfo_r_send(tctx, tctx->ev, p->binding_handle, &r); + + /* even with this flush per request a w2k3 server seems to + clag with multiple outstanding requests. bleergh. */ + torture_assert_int_equal(tctx, tevent_loop_once(tctx->ev), 0, + "tevent_loop_once failed"); + } + + for (i=0;i<ASYNC_COUNT;i++) { + torture_assert_int_equal(tctx, tevent_req_poll(req[i], tctx->ev), true, + "tevent_req_poll() failed"); + + status = dcerpc_netr_LogonGetDomainInfo_r_recv(req[i], tctx); + + torture_assert_ntstatus_ok(tctx, status, "netr_LogonGetDomainInfo_async"); + torture_assert_ntstatus_ok(tctx, r.out.result, "netr_LogonGetDomainInfo_async"); + + torture_assert(tctx, netlogon_creds_client_check(creds_async[i], &a.cred), + "Credential chaining failed at async"); + } + + torture_comment(tctx, + "Testing netr_LogonGetDomainInfo - async count %d OK\n", ASYNC_COUNT); + + return true; +} + +static bool test_ManyGetDCName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct cli_credentials *anon_creds; + struct dcerpc_binding *binding2; + struct dcerpc_pipe *p2; + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 o; + struct policy_handle lsa_handle; + struct lsa_DomainList domains; + + struct lsa_EnumTrustDom t; + uint32_t resume_handle = 0; + struct netr_GetAnyDCName d; + const char *dcname = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_binding_handle *b2; + + int i; + + if (p->conn->transport.transport != NCACN_NP) { + torture_skip(tctx, "test_ManyGetDCName works only with NCACN_NP"); + } + + torture_comment(tctx, "Torturing GetDCName\n"); + + anon_creds = cli_credentials_init_anon(tctx); + torture_assert(tctx, anon_creds != NULL, "cli_credentials_init_anon failed"); + + binding2 = dcerpc_binding_dup(tctx, p->binding); + /* Swap the binding details from NETLOGON to LSA */ + status = dcerpc_epm_map_binding(tctx, binding2, &ndr_table_lsarpc, tctx->ev, tctx->lp_ctx); + dcerpc_binding_set_assoc_group_id(binding2, 0); + torture_assert_ntstatus_ok(tctx, status, "epm map"); + + status = dcerpc_secondary_auth_connection(p, binding2, &ndr_table_lsarpc, + anon_creds, tctx->lp_ctx, + tctx, &p2); + torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection"); + b2 = p2->binding_handle; + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + o.in.system_name = "\\"; + o.in.attr = &attr; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &lsa_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenPolicy2_r(b2, tctx, &o), + "OpenPolicy2 failed"); + torture_assert_ntstatus_ok(tctx, o.out.result, "OpenPolicy2 failed"); + + t.in.handle = &lsa_handle; + t.in.resume_handle = &resume_handle; + t.in.max_size = 1000; + t.out.domains = &domains; + t.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumTrustDom_r(b2, tctx, &t), + "EnumTrustDom failed"); + + if ((!NT_STATUS_IS_OK(t.out.result) && + (!NT_STATUS_EQUAL(t.out.result, NT_STATUS_NO_MORE_ENTRIES)))) + torture_fail(tctx, "Could not list domains"); + + talloc_free(p2); + + d.in.logon_server = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + d.out.dcname = &dcname; + + for (i=0; i<domains.count * 4; i++) { + struct lsa_DomainInfo *info = + &domains.domains[rand()%domains.count]; + + d.in.domainname = info->name.string; + + status = dcerpc_netr_GetAnyDCName_r(b, tctx, &d); + torture_assert_ntstatus_ok(tctx, status, "GetAnyDCName"); + + torture_comment(tctx, "\tDC for domain %s is %s\n", info->name.string, + dcname ? dcname : "unknown"); + } + + return true; +} + +static bool test_lsa_over_netlogon(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct cli_credentials *anon_creds; + const struct dcerpc_binding *binding2; + struct dcerpc_pipe *p2; + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 o; + struct policy_handle lsa_handle; + + struct dcerpc_binding_handle *b2; + + + if (p->conn->transport.transport != NCACN_NP) { + torture_skip(tctx, "test_lsa_over_netlogon works only with NCACN_NP"); + } + + torture_comment(tctx, "Testing if we can access the LSA server over\n" + " \\\\pipe\\netlogon rather than \\\\pipe\\lsarpc\n"); + + anon_creds = cli_credentials_init_anon(tctx); + torture_assert(tctx, anon_creds != NULL, "cli_credentials_init_anon failed"); + + binding2 = p->binding; + + status = dcerpc_secondary_auth_connection(p, binding2, &ndr_table_lsarpc, + anon_creds, tctx->lp_ctx, + tctx, &p2); + torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection"); + b2 = p2->binding_handle; + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + o.in.system_name = "\\"; + o.in.attr = &attr; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &lsa_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenPolicy2_r(b2, tctx, &o), + "OpenPolicy2 failed"); + torture_assert_ntstatus_ok(tctx, o.out.result, "OpenPolicy2 failed"); + + talloc_free(p2); + + return true; +} + +static bool test_SetPassword_with_flags(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + uint32_t flags[] = { 0, NETLOGON_NEG_STRONG_KEYS }; + struct netlogon_creds_CredentialState *creds; + int i; + + if (!test_SetupCredentials2(p, tctx, 0, + machine_credentials, + cli_credentials_get_secure_channel_type(machine_credentials), + &creds)) { + torture_skip(tctx, "DC does not support negotiation of 64bit session keys"); + } + + for (i=0; i < ARRAY_SIZE(flags); i++) { + torture_assert(tctx, + test_SetPassword_flags(tctx, p, machine_credentials, flags[i]), + talloc_asprintf(tctx, "failed to test SetPassword negotiating with 0x%08x flags", flags[i])); + } + + return true; +} + +struct torture_suite *torture_rpc_netlogon(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "netlogon"); + struct torture_rpc_tcase *tcase; + struct torture_test *test; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netlogon", + &ndr_table_netlogon, TEST_MACHINE_NAME); + + torture_rpc_tcase_add_test_creds(tcase, "SetupCredentialsDowngrade", test_SetupCredentialsDowngrade); + torture_rpc_tcase_add_test(tcase, "lsa_over_netlogon", test_lsa_over_netlogon); + + torture_rpc_tcase_add_test_creds(tcase, "GetForestTrustInformation", test_netr_GetForestTrustInformation); + torture_rpc_tcase_add_test_creds(tcase, "ServerGetTrustInfo_AES", test_netr_ServerGetTrustInfo_AES); + torture_rpc_tcase_add_test_creds(tcase, "ServerGetTrustInfo", test_netr_ServerGetTrustInfo); + torture_rpc_tcase_add_test(tcase, "DsRAddressToSitenamesExW", test_netr_DsRAddressToSitenamesExW); + torture_rpc_tcase_add_test(tcase, "DsRAddressToSitenamesW", test_netr_DsRAddressToSitenamesW); + torture_rpc_tcase_add_test(tcase, "DsrGetDcSiteCoverageW", test_netr_DsrGetDcSiteCoverageW); + torture_rpc_tcase_add_test(tcase, "DsRGetDCNameEx2", test_netr_DsRGetDCNameEx2); + torture_rpc_tcase_add_test(tcase, "DsRGetDCNameEx", test_netr_DsRGetDCNameEx); + torture_rpc_tcase_add_test(tcase, "DsRGetDCName", test_netr_DsRGetDCName); + test = torture_rpc_tcase_add_test_creds(tcase, "GetDomainInfo_async", test_GetDomainInfo_async); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "NetrEnumerateTrustedDomainsEx", test_netr_NetrEnumerateTrustedDomainsEx); + torture_rpc_tcase_add_test(tcase, "NetrEnumerateTrustedDomains", test_netr_NetrEnumerateTrustedDomains); + torture_rpc_tcase_add_test(tcase, "DsrEnumerateDomainTrusts", test_DsrEnumerateDomainTrusts); + torture_rpc_tcase_add_test_creds(tcase, "DatabaseSync2", test_DatabaseSync2); + torture_rpc_tcase_add_test(tcase, "GetAnyDCName", test_GetAnyDCName); + torture_rpc_tcase_add_test(tcase, "ManyGetDCName", test_ManyGetDCName); + torture_rpc_tcase_add_test(tcase, "GetDcName", test_GetDcName); + torture_rpc_tcase_add_test_creds(tcase, "AccountSync", test_AccountSync); + torture_rpc_tcase_add_test_creds(tcase, "AccountDeltas", test_AccountDeltas); + torture_rpc_tcase_add_test_creds(tcase, "DatabaseRedo", test_DatabaseRedo); + torture_rpc_tcase_add_test_creds(tcase, "DatabaseDeltas", test_DatabaseDeltas); + torture_rpc_tcase_add_test_creds(tcase, "DatabaseSync", test_DatabaseSync); + torture_rpc_tcase_add_test_creds(tcase, "GetDomainInfo", test_GetDomainInfo); + torture_rpc_tcase_add_test_creds(tcase, "GetTrustPasswords", test_GetTrustPasswords); + torture_rpc_tcase_add_test_creds(tcase, "GetPassword", test_GetPassword); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword2_AES", test_SetPassword2_AES); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword2", test_SetPassword2); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword", test_SetPassword); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeReuse", test_ServerReqChallengeReuse); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeReuseGlobal4", test_ServerReqChallengeReuseGlobal4); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeReuseGlobal3", test_ServerReqChallengeReuseGlobal3); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeReuseGlobal2", test_ServerReqChallengeReuseGlobal2); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeReuseGlobal", test_ServerReqChallengeReuseGlobal); + torture_rpc_tcase_add_test_creds(tcase, "ServerReqChallengeGlobal", test_ServerReqChallengeGlobal); + torture_rpc_tcase_add_test_creds(tcase, "invalidAuthenticate2", test_invalidAuthenticate2); + torture_rpc_tcase_add_test_creds(tcase, "SamLogon", test_SamLogon); + torture_rpc_tcase_add_test(tcase, "LogonUasLogoff", test_LogonUasLogoff); + torture_rpc_tcase_add_test(tcase, "LogonUasLogon", test_LogonUasLogon); + + torture_rpc_tcase_add_test(tcase, "Broken RPC binding handle", + test_netr_broken_binding_handle); + + return suite; +} + +struct torture_suite *torture_rpc_netlogon_s3(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "netlogon-s3"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netlogon", + &ndr_table_netlogon, TEST_MACHINE_NAME); + + torture_rpc_tcase_add_test_creds(tcase, "SamLogon", test_SamLogon); + torture_rpc_tcase_add_test_creds(tcase, "SamLogon_NULL_domain", test_SamLogon_NULL_domain); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword", test_SetPassword); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword_with_flags", test_SetPassword_with_flags); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword2", test_SetPassword2); + torture_rpc_tcase_add_test_creds(tcase, "SetPassword2_AES", test_SetPassword2_AES); + torture_rpc_tcase_add_test(tcase, "NetrEnumerateTrustedDomains", test_netr_NetrEnumerateTrustedDomains); + + return suite; +} + +struct torture_suite *torture_rpc_netlogon_zerologon(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create( + mem_ctx, + "netlogon.zerologon"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase( + suite, + "netlogon", + &ndr_table_netlogon, + TEST_MACHINE_NAME); + + torture_rpc_tcase_add_test_creds( + tcase, + "ServerReqChallenge", + test_ServerReqChallenge); + torture_rpc_tcase_add_test_creds( + tcase, + "ServerReqChallenge_zero_challenge", + test_ServerReqChallenge_zero_challenge); + torture_rpc_tcase_add_test_creds( + tcase, + "ServerReqChallenge_5_repeats", + test_ServerReqChallenge_5_repeats); + torture_rpc_tcase_add_test_creds( + tcase, + "ServerReqChallenge_4_repeats", + test_ServerReqChallenge_4_repeats); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_encrypted_to_all_zeros", + test_SetPassword2_encrypted_to_all_zeros); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_password_encrypts_to_zero", + test_SetPassword2_password_encrypts_to_zero); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_confounder", + test_SetPassword2_confounder); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_all_zeros", + test_SetPassword2_all_zeros); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_all_zero_password", + test_SetPassword2_all_zero_password); + torture_rpc_tcase_add_test_creds( + tcase, + "test_SetPassword2_maximum_length_password", + test_SetPassword2_maximum_length_password); + + return suite; +} + +struct torture_suite *torture_rpc_netlogon_admin(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "netlogon.admin"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "bdc", + &ndr_table_netlogon, TEST_MACHINE_NAME); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl", test_LogonControl); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2", test_LogonControl2); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2Ex", test_LogonControl2Ex); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "wkst", + &ndr_table_netlogon, TEST_MACHINE_NAME); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl", test_LogonControl); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2", test_LogonControl2); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2Ex", test_LogonControl2Ex); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "admin", + &ndr_table_netlogon); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl", test_LogonControl); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2", test_LogonControl2); + torture_rpc_tcase_add_test_creds(tcase, "LogonControl2Ex", test_LogonControl2Ex); + + return suite; +} diff --git a/source4/torture/rpc/netlogon.h b/source4/torture/rpc/netlogon.h new file mode 100644 index 0000000..a4ab8f0 --- /dev/null +++ b/source4/torture/rpc/netlogon.h @@ -0,0 +1,37 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 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/>. +*/ + +bool test_SetupCredentials2(struct dcerpc_pipe *p, struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + int sec_chan_type, + struct netlogon_creds_CredentialState **creds_out); + +bool test_SetupCredentials3(struct dcerpc_pipe *p, struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + struct netlogon_creds_CredentialState **creds_out); + +bool test_SetupCredentialsPipe(const struct dcerpc_pipe *p1, + struct torture_context *tctx, + struct cli_credentials *machine_credentials, + struct netlogon_creds_CredentialState *creds, + uint32_t additional_flags, + struct dcerpc_pipe **_p2); diff --git a/source4/torture/rpc/netlogon_crypto.c b/source4/torture/rpc/netlogon_crypto.c new file mode 100644 index 0000000..8584460 --- /dev/null +++ b/source4/torture/rpc/netlogon_crypto.c @@ -0,0 +1,274 @@ +/* + Unix SMB/CIFS implementation. + + test suite for netlogon rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004 + Copyright (C) Tim Potter 2003 + 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 "lib/replace/system/network.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "param/param.h" +#include "lib/param/loadparm.h" +#include "libcli/security/security.h" + +#undef strcasecmp + +#define TEST_MACHINE_NAME "torturetest" + +static bool test_ServerAuth3Crypto(struct dcerpc_pipe *p, + struct torture_context *tctx, + uint32_t negotiate_flags, + struct cli_credentials *machine_credentials, + bool force_client_rc4) +{ + struct netr_ServerReqChallenge r; + struct netr_ServerAuthenticate3 a; + struct netr_Credential netr_creds1 = { + .data = {0}, + }; + struct netr_Credential netr_creds2 = { + .data = {0}, + }; + struct netr_Credential netr_creds3 = { + .data = {0}, + }; + struct netlogon_creds_CredentialState *creds_state = NULL; + struct samr_Password machine_password = { + .hash = {0}, + }; + const char *machine_name = NULL; + const char *plain_pass = NULL; + struct dcerpc_binding_handle *b = NULL; + uint32_t rid = 0; + NTSTATUS status; + bool weak_crypto_allowed = + (lpcfg_weak_crypto(tctx->lp_ctx) == + SAMBA_WEAK_CRYPTO_ALLOWED); + + if (p == NULL) { + return false; + } + b = p->binding_handle; + + ZERO_STRUCT(r); + ZERO_STRUCT(a); + + torture_comment(tctx, "client negotiate_flags=0x%08x\n", negotiate_flags); + + machine_name = cli_credentials_get_workstation(machine_credentials); + torture_assert_not_null(tctx, machine_name, "machine name is not set"); + + plain_pass = cli_credentials_get_password(machine_credentials); + torture_assert_not_null(tctx, plain_pass, "plain_pass is not set"); + + + torture_comment(tctx, "Testing ServerReqChallenge\n"); + + r.in.server_name = NULL; + r.in.computer_name = machine_name; + r.in.credentials = &netr_creds1; + r.out.return_credentials = &netr_creds2; + + netlogon_creds_random_challenge(&netr_creds1); + + status = dcerpc_netr_ServerReqChallenge_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, + status, + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, + r.out.result, + "ServerReqChallenge failed"); + + E_md4hash(plain_pass, machine_password.hash); + + a.in.server_name = NULL; + a.in.account_name = talloc_asprintf(tctx, "%s$", machine_name); + a.in.secure_channel_type = + cli_credentials_get_secure_channel_type(machine_credentials); + a.in.computer_name = machine_name; + a.in.negotiate_flags = &negotiate_flags; + a.in.credentials = &netr_creds3; + a.out.return_credentials = &netr_creds3; + a.out.negotiate_flags = &negotiate_flags; + a.out.rid = &rid; + + if (force_client_rc4) { + GNUTLS_FIPS140_SET_LAX_MODE(); + } + creds_state = netlogon_creds_client_init(tctx, + a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + &netr_creds1, + &netr_creds2, + &machine_password, + &netr_creds3, + negotiate_flags); + GNUTLS_FIPS140_SET_STRICT_MODE(); + /* Test that we fail to encrypt with RC4 */ + if (creds_state == NULL && + !weak_crypto_allowed && !force_client_rc4 && + (negotiate_flags & NETLOGON_NEG_ARCFOUR)) { + return false; + } + torture_assert_not_null(tctx, + creds_state, + "Failed init netlogon client creds"); + + + torture_comment(tctx, "Testing ServerAuthenticate3\n"); + + status = dcerpc_netr_ServerAuthenticate3_r(b, tctx, &a); + torture_assert_ntstatus_ok(tctx, + status, + "ServerAuthenticate3 failed"); + + /* Check that the server denies RC4 */ + if (!NT_STATUS_IS_OK(a.out.result) && + !weak_crypto_allowed && + force_client_rc4) { + torture_assert_ntstatus_equal(tctx, + a.out.result, + NT_STATUS_DOWNGRADE_DETECTED, + "Unexpected status code"); + return false; + } + torture_assert_ntstatus_ok(tctx, + a.out.result, + "ServerAuthenticate3 failed"); + torture_assert(tctx, + netlogon_creds_client_check(creds_state, &netr_creds3), + "Credential chaining failed"); + + torture_comment(tctx, + "server negotiate_flags=0x%08x\n", + negotiate_flags); + + if (!weak_crypto_allowed) { + torture_assert(tctx, + (negotiate_flags & NETLOGON_NEG_ARCFOUR) == 0, + "Server should not announce RC4 support"); + } + + /* Prove that requesting a challenge again won't break it */ + torture_assert_ntstatus_ok(tctx, dcerpc_netr_ServerReqChallenge_r(b, tctx, &r), + "ServerReqChallenge failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "ServerReqChallenge failed"); + + return true; +} + + +/* Test that we can successfully authenticate using AES. */ +static bool test_AES_Crypto(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + uint32_t negotiate_flags = + NETLOGON_NEG_AUTH2_ADS_FLAGS| + NETLOGON_NEG_SUPPORTS_AES; + bool ok; + + ok = test_ServerAuth3Crypto(p, + tctx, + negotiate_flags, + machine_credentials, + false); + if (!ok) { + return false; + } + + return true; +} + +/* If we try to use RC4, the client code should fail to encrypt. */ +static bool test_RC4_Crypto_Fail(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + uint32_t negotiate_flags = + NETLOGON_NEG_AUTH2_ADS_FLAGS| + NETLOGON_NEG_ARCFOUR; + bool ok; + + ok = test_ServerAuth3Crypto(p, + tctx, + negotiate_flags, + machine_credentials, + false); + if (!ok) { + return true; + } + + return false; +} + +/* + * Enforce the use of RC4 and try to authenticate. The server should fail + * in this case as it doesn't allow RC4 + */ +static bool test_RC4_Crypto_Force(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + uint32_t negotiate_flags = + NETLOGON_NEG_AUTH2_ADS_FLAGS| + NETLOGON_NEG_ARCFOUR; + bool ok; + + ok = test_ServerAuth3Crypto(p, + tctx, + negotiate_flags, + machine_credentials, + true); + if (!ok) { + return true; + } + + return false; +} + +struct torture_suite *torture_rpc_netlogon_crypto_fips(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, + "fips.netlogon.crypto"); + struct torture_rpc_tcase *tcase = NULL; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, + "netlogon", + &ndr_table_netlogon, + TEST_MACHINE_NAME); + + torture_rpc_tcase_add_test_creds(tcase, + "test_AES_Crytpo", + test_AES_Crypto); + torture_rpc_tcase_add_test_creds(tcase, + "test_RC4_Crytpo_Fail", + test_RC4_Crypto_Fail); + torture_rpc_tcase_add_test_creds(tcase, + "test_RC4_Crytpo_Force", + test_RC4_Crypto_Force); + + return suite; +} diff --git a/source4/torture/rpc/ntsvcs.c b/source4/torture/rpc/ntsvcs.c new file mode 100644 index 0000000..a25129d --- /dev/null +++ b/source4/torture/rpc/ntsvcs.c @@ -0,0 +1,189 @@ +/* + Unix SMB/CIFS implementation. + test suite for rpc ntsvcs operations + + Copyright (C) Guenther Deschner 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_ntsvcs_c.h" + +static bool test_PNP_GetVersion(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + struct PNP_GetVersion r; + uint16_t version = 0; + + r.out.version = &version; + + status = dcerpc_PNP_GetVersion_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "PNP_GetVersion"); + torture_assert_werr_ok(tctx, r.out.result, "PNP_GetVersion"); + torture_assert_int_equal(tctx, version, 0x400, "invalid version"); + + return true; +} + +static bool test_PNP_GetDeviceListSize(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct PNP_GetDeviceListSize r; + uint32_t size = 0; + + r.in.devicename = NULL; + r.in.flags = CM_GETIDLIST_FILTER_SERVICE; + r.out.size = &size; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceListSize_r(b, tctx, &r), + "PNP_GetDeviceListSize"); + torture_assert_werr_equal(tctx, r.out.result, WERR_CM_INVALID_POINTER, + "PNP_GetDeviceListSize"); + + r.in.devicename = "Spooler"; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceListSize_r(b, tctx, &r), + "PNP_GetDeviceListSize"); + torture_assert_werr_ok(tctx, r.out.result, + "PNP_GetDeviceListSize"); + + return true; +} + +static bool test_PNP_GetDeviceList(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct PNP_GetDeviceList r; + uint16_t *buffer = NULL; + uint32_t length = 0; + + buffer = talloc_array(tctx, uint16_t, 0); + + r.in.filter = NULL; + r.in.flags = CM_GETIDLIST_FILTER_SERVICE; + r.in.length = &length; + r.out.length = &length; + r.out.buffer = buffer; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceList_r(b, tctx, &r), + "PNP_GetDeviceList failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_CM_INVALID_POINTER, + "PNP_GetDeviceList failed"); + + r.in.filter = "Spooler"; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceList_r(b, tctx, &r), + "PNP_GetDeviceList failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_CM_BUFFER_SMALL)) { + struct PNP_GetDeviceListSize s; + + s.in.devicename = "Spooler"; + s.in.flags = CM_GETIDLIST_FILTER_SERVICE; + s.out.size = &length; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceListSize_r(b, tctx, &s), + "PNP_GetDeviceListSize failed"); + torture_assert_werr_ok(tctx, s.out.result, + "PNP_GetDeviceListSize failed"); + } + + buffer = talloc_array(tctx, uint16_t, length); + + r.in.length = &length; + r.out.length = &length; + r.out.buffer = buffer; + + torture_assert_ntstatus_ok(tctx, + dcerpc_PNP_GetDeviceList_r(b, tctx, &r), + "PNP_GetDeviceList failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "PNP_GetDeviceList failed"); + + return true; +} + +static bool test_PNP_GetDeviceRegProp(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + struct PNP_GetDeviceRegProp r; + + enum winreg_Type reg_data_type = REG_NONE; + uint32_t buffer_size = 0; + uint32_t needed = 0; + uint8_t *buffer; + + buffer = talloc(tctx, uint8_t); + + r.in.devicepath = "ACPI\\ACPI0003\\1"; + r.in.property = DEV_REGPROP_DESC; + r.in.flags = 0; + r.in.reg_data_type = ®_data_type; + r.in.buffer_size = &buffer_size; + r.in.needed = &needed; + r.out.buffer = buffer; + r.out.reg_data_type = ®_data_type; + r.out.buffer_size = &buffer_size; + r.out.needed = &needed; + + status = dcerpc_PNP_GetDeviceRegProp_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "PNP_GetDeviceRegProp"); + + if (W_ERROR_EQUAL(r.out.result, WERR_CM_BUFFER_SMALL)) { + + buffer = talloc_array(tctx, uint8_t, needed); + r.in.buffer_size = &needed; + + status = dcerpc_PNP_GetDeviceRegProp_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "PNP_GetDeviceRegProp"); + } + + return true; +} + +struct torture_suite *torture_rpc_ntsvcs(TALLOC_CTX *mem_ctx) +{ + struct torture_rpc_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "ntsvcs"); + + tcase = torture_suite_add_rpc_iface_tcase(suite, "ntsvcs", + &ndr_table_ntsvcs); + + torture_rpc_tcase_add_test(tcase, "PNP_GetDeviceRegProp", + test_PNP_GetDeviceRegProp); + torture_rpc_tcase_add_test(tcase, "PNP_GetDeviceList", + test_PNP_GetDeviceList); + torture_rpc_tcase_add_test(tcase, "PNP_GetDeviceListSize", + test_PNP_GetDeviceListSize); + torture_rpc_tcase_add_test(tcase, "PNP_GetVersion", + test_PNP_GetVersion); + + return suite; +} diff --git a/source4/torture/rpc/object_uuid.c b/source4/torture/rpc/object_uuid.c new file mode 100644 index 0000000..2209954 --- /dev/null +++ b/source4/torture/rpc/object_uuid.c @@ -0,0 +1,85 @@ +/* + Unix SMB/CIFS implementation. + + test suite for behaviour of object uuids in rpc requests + + 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 "librpc/gen_ndr/ndr_dssetup.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "torture/rpc/torture_rpc.h" + +/* + this tests the send object uuids in the dcerpc request +*/ + +static bool test_random_uuid(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p1, *p2; + struct GUID uuid; + struct dssetup_DsRoleGetPrimaryDomainInformation r1; + struct lsa_GetUserName r2; + struct lsa_String *authority_name_p = NULL; + struct lsa_String *account_name_p = NULL; + + torture_comment(torture, "RPC-OBJECTUUID-RANDOM\n"); + + status = torture_rpc_connection(torture, &p1, &ndr_table_dssetup); + torture_assert_ntstatus_ok(torture, status, "opening dsetup pipe1"); + + status = torture_rpc_connection(torture, &p2, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(torture, status, "opening lsa pipe1"); + + uuid = GUID_random(); + + r1.in.level = DS_ROLE_BASIC_INFORMATION; + status = dcerpc_binding_handle_call(p1->binding_handle, + &uuid, + &ndr_table_dssetup, + NDR_DSSETUP_DSROLEGETPRIMARYDOMAININFORMATION, + torture, &r1); + torture_assert_ntstatus_ok(torture, status, "DsRoleGetPrimaryDomainInformation failed"); + torture_assert_werr_ok(torture, r1.out.result, "DsRoleGetPrimaryDomainInformation failed"); + + uuid = GUID_random(); + + r2.in.system_name = "\\"; + r2.in.account_name = &account_name_p; + r2.in.authority_name = &authority_name_p; + r2.out.account_name = &account_name_p; + r2.out.authority_name = &authority_name_p; + + status = dcerpc_binding_handle_call(p2->binding_handle, + &uuid, + &ndr_table_lsarpc, + NDR_LSA_GETUSERNAME, + torture, &r2); + torture_assert_ntstatus_ok(torture, status, "lsaClose failed"); + torture_assert_ntstatus_ok(torture, r2.out.result, "lsaClose failed"); + + return true; +} + +struct torture_suite *torture_rpc_object_uuid(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + suite = torture_suite_create(mem_ctx, "objectuuid"); + torture_suite_add_simple_test(suite, "random-uuid", test_random_uuid); + return suite; +} diff --git a/source4/torture/rpc/oxidresolve.c b/source4/torture/rpc/oxidresolve.c new file mode 100644 index 0000000..11cd8fe --- /dev/null +++ b/source4/torture/rpc/oxidresolve.c @@ -0,0 +1,263 @@ +/* + Unix SMB/CIFS implementation. + test suite for oxidresolve operations + + 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_oxidresolver_c.h" +#include "librpc/gen_ndr/ndr_remact_c.h" +#include "librpc/gen_ndr/epmapper.h" +#include "torture/rpc/torture_rpc.h" + +#define CLSID_IMAGEDOC "02B01C80-E03D-101A-B294-00DD010F2BF9" + +const struct GUID IUnknown_uuid = { + 0x00000000,0x0000,0x0000,{0xc0,0x00},{0x00,0x00,0x00,0x00,0x00,0x46} +}; + +static bool test_RemoteActivation(struct torture_context *tctx, + uint64_t *oxid, struct GUID *oid) +{ + struct RemoteActivation r; + NTSTATUS status; + struct GUID iids[2]; + uint16_t protseq[3] = { EPM_PROTOCOL_TCP, EPM_PROTOCOL_NCALRPC, EPM_PROTOCOL_UUID }; + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + struct ORPCTHAT that; + struct DUALSTRINGARRAY *pdsaOxidBindings; + uint32_t AuthnHint; + struct COMVERSION ServerVersion; + HRESULT hr; + struct MInterfacePointer *ifaces; + + status = torture_rpc_connection(tctx, &p, + &ndr_table_IRemoteActivation); + + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ZERO_STRUCT(r); + + r.in.this_object.version.MajorVersion = 5; + r.in.this_object.version.MinorVersion = 1; + r.in.this_object.cid = GUID_random(); + GUID_from_string(CLSID_IMAGEDOC, &r.in.Clsid); + r.in.ClientImpLevel = RPC_C_IMP_LEVEL_IDENTIFY; + r.in.num_protseqs = 3; + r.in.protseq = protseq; + r.in.Interfaces = 1; + iids[0] = IUnknown_uuid; + r.in.pIIDs = iids; + + r.out.that = &that; + r.out.pOxid = oxid; + r.out.pdsaOxidBindings = &pdsaOxidBindings; + r.out.ipidRemUnknown = oid; + r.out.AuthnHint = &AuthnHint; + r.out.ServerVersion = &ServerVersion; + r.out.hr = &hr; + r.out.ifaces = &ifaces; + + status = dcerpc_RemoteActivation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "RemoteActivation failed"); + torture_assert_werr_ok(tctx, r.out.result, "RemoteActivation failed"); + torture_assert_hresult_ok(tctx, *r.out.hr, "RemoteActivation failed"); + torture_assert_hresult_ok(tctx, r.out.results[0], "RemoteActivation failed"); + + return true; +} + +static bool test_SimplePing(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct SimplePing r; + NTSTATUS status; + uint64_t setid; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.SetId = &setid; + + status = dcerpc_SimplePing_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "SimplePing"); + torture_assert_werr_ok(tctx, r.out.result, "SimplePing"); + + return true; +} + +static bool test_ComplexPing(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct ComplexPing r; + NTSTATUS status; + uint64_t setid; + struct GUID oid; + uint64_t oxid; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_RemoteActivation(tctx, &oxid, &oid)) + return false; + + setid = 0; + ZERO_STRUCT(r.in); + + r.in.SequenceNum = 0; + r.in.SetId = &setid; + r.in.cAddToSet = 1; + r.in.AddToSet = &oid; + + status = dcerpc_ComplexPing_r(b, tctx, &r); + if(NT_STATUS_IS_ERR(status)) { + fprintf(stderr, "ComplexPing: %s\n", nt_errstr(status)); + return 0; + } + + if(!W_ERROR_IS_OK(r.out.result)) { + fprintf(stderr, "ComplexPing: %s\n", win_errstr(r.out.result)); + return 0; + } + + + + return 1; +} + +static bool test_ServerAlive(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct ServerAlive r; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + status = dcerpc_ServerAlive_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ServerAlive"); + torture_assert_werr_ok(tctx, r.out.result, "ServerAlive"); + + return true; +} + +static bool test_ResolveOxid(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct ResolveOxid r; + NTSTATUS status; + uint16_t protseq[2] = { EPM_PROTOCOL_TCP, EPM_PROTOCOL_SMB }; + uint64_t oxid; + struct GUID oid; + struct dcerpc_binding_handle *b = p->binding_handle; + struct DUALSTRINGARRAY *ppdsaOxidBindings; + struct GUID pipidRemUnknown; + uint32_t pAuthnHint; + + if (!test_RemoteActivation(tctx, &oxid, &oid)) + return false; + + r.in.pOxid = oxid; + r.in.cRequestedProtseqs = 2; + r.in.arRequestedProtseqs = protseq; + r.out.ppdsaOxidBindings = &ppdsaOxidBindings; + r.out.pipidRemUnknown = &pipidRemUnknown; + r.out.pAuthnHint = &pAuthnHint; + + status = dcerpc_ResolveOxid_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ResolveOxid"); + torture_assert_werr_ok(tctx, r.out.result, "ResolveOxid"); + + return true; +} + +static bool test_ResolveOxid2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct ResolveOxid2 r; + NTSTATUS status; + uint16_t protseq[2] = { EPM_PROTOCOL_TCP, EPM_PROTOCOL_SMB }; + uint64_t oxid; + struct GUID oid; + struct dcerpc_binding_handle *b = p->binding_handle; + struct DUALSTRINGARRAY *pdsaOxidBindings; + struct GUID ipidRemUnknown; + uint32_t AuthnHint; + struct COMVERSION ComVersion; + + if (!test_RemoteActivation(tctx, &oxid, &oid)) + return false; + + r.in.pOxid = oxid; + r.in.cRequestedProtseqs = 2; + r.in.arRequestedProtseqs = protseq; + r.out.pdsaOxidBindings = &pdsaOxidBindings; + r.out.ipidRemUnknown = &ipidRemUnknown; + r.out.AuthnHint = &AuthnHint; + r.out.ComVersion = &ComVersion; + + status = dcerpc_ResolveOxid2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ResolveOxid2"); + + torture_assert_werr_ok(tctx, r.out.result, "ResolveOxid2"); + + torture_comment(tctx, "Remote server versions: %d, %d\n", r.out.ComVersion->MajorVersion, r.out.ComVersion->MinorVersion); + + return true; +} + +static bool test_ServerAlive2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct ServerAlive2 r; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + struct COMINFO info; + struct DUALSTRINGARRAY *dualstring; + uint8_t pReserved; + + r.out.info = &info; + r.out.dualstring = &dualstring; + r.out.pReserved = &pReserved; + + status = dcerpc_ServerAlive2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ServerAlive2"); + torture_assert_werr_ok(tctx, r.out.result, "ServerAlive2"); + + return true; +} + +struct torture_suite *torture_rpc_oxidresolve(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "oxidresolve"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "oxidresolver", + &ndr_table_IOXIDResolver); + + torture_rpc_tcase_add_test(tcase, "ServerAlive", test_ServerAlive); + + torture_rpc_tcase_add_test(tcase, "ServerAlive2", test_ServerAlive2); + + torture_rpc_tcase_add_test(tcase, "ComplexPing", test_ComplexPing); + + torture_rpc_tcase_add_test(tcase, "SimplePing", test_SimplePing); + + torture_rpc_tcase_add_test(tcase, "ResolveOxid", test_ResolveOxid); + + torture_rpc_tcase_add_test(tcase, "ResolveOxid2", test_ResolveOxid2); + + return suite; +} diff --git a/source4/torture/rpc/remact.c b/source4/torture/rpc/remact.c new file mode 100644 index 0000000..9e9642e --- /dev/null +++ b/source4/torture/rpc/remact.c @@ -0,0 +1,104 @@ +/* + Unix SMB/CIFS implementation. + test suite for remoteactivation operations + + 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_remact_c.h" +#include "librpc/gen_ndr/ndr_epmapper_c.h" +#include "torture/rpc/torture_rpc.h" + +#define CLSID_IMAGEDOC "02B01C80-E03D-101A-B294-00DD010F2BF9" +#define DCERPC_IUNKNOWN_UUID "00000000-0000-0000-c000-000000000046" +#define DCERPC_ICLASSFACTORY_UUID "00000001-0000-0000-c000-000000000046" + +static bool test_RemoteActivation(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct RemoteActivation r; + NTSTATUS status; + struct GUID iids[1]; + uint16_t protseq[3] = { EPM_PROTOCOL_TCP, EPM_PROTOCOL_NCALRPC, EPM_PROTOCOL_UUID }; + struct dcerpc_binding_handle *b = p->binding_handle; + struct ORPCTHAT that; + uint64_t pOxid; + struct DUALSTRINGARRAY *pdsaOxidBindings; + struct GUID ipidRemUnknown; + uint32_t AuthnHint; + struct COMVERSION ServerVersion; + HRESULT hr; + struct MInterfacePointer *ifaces; + + ZERO_STRUCT(r); + + r.in.this_object.version.MajorVersion = 5; + r.in.this_object.version.MinorVersion = 1; + r.in.this_object.cid = GUID_random(); + GUID_from_string(CLSID_IMAGEDOC, &r.in.Clsid); + r.in.ClientImpLevel = RPC_C_IMP_LEVEL_IDENTIFY; + r.in.num_protseqs = 3; + r.in.protseq = protseq; + r.in.Interfaces = 1; + GUID_from_string(DCERPC_IUNKNOWN_UUID, &iids[0]); + r.in.pIIDs = iids; + + r.out.that = &that; + r.out.pOxid = &pOxid; + r.out.pdsaOxidBindings = &pdsaOxidBindings; + r.out.ipidRemUnknown = &ipidRemUnknown; + r.out.AuthnHint = &AuthnHint; + r.out.ServerVersion = &ServerVersion; + r.out.hr = &hr; + r.out.ifaces = &ifaces; + + status = dcerpc_RemoteActivation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "RemoteActivation"); + + torture_assert_werr_ok(tctx, r.out.result, "RemoteActivation"); + + torture_assert_hresult_ok(tctx, *r.out.hr, "RemoteActivation"); + + torture_assert_hresult_ok(tctx, r.out.results[0], "RemoteActivation"); + + GUID_from_string(DCERPC_ICLASSFACTORY_UUID, &iids[0]); + r.in.Interfaces = 1; + r.in.Mode = MODE_GET_CLASS_OBJECT; + + status = dcerpc_RemoteActivation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "RemoteActivation(GetClassObject)"); + + torture_assert_werr_ok(tctx, r.out.result, "RemoteActivation(GetClassObject)"); + + torture_assert_hresult_ok(tctx, *r.out.hr, "RemoteActivation(GetClassObject)"); + + torture_assert_hresult_ok(tctx, r.out.results[0], "RemoteActivation(GetClassObject)"); + + return true; +} + +struct torture_suite *torture_rpc_remact(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "remact"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "remact", &ndr_table_IRemoteActivation); + + torture_rpc_tcase_add_test(tcase, "RemoteActivation", test_RemoteActivation); + + return suite; +} diff --git a/source4/torture/rpc/remote_pac.c b/source4/torture/rpc/remote_pac.c new file mode 100644 index 0000000..c306006 --- /dev/null +++ b/source4/torture/rpc/remote_pac.c @@ -0,0 +1,1372 @@ +/* + Unix SMB/CIFS implementation. + + test suite for netlogon PAC operations + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2012 + + 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/auth_sam_reply.h" +#include "auth/gensec/gensec.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/auth/libcli_auth.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "param/param.h" +#include <ldb.h> +#include "ldb_wrap.h" +#include "dsdb/samdb/samdb.h" + +#define TEST_MACHINE_NAME_BDC "torturepacbdc" +#define TEST_MACHINE_NAME_WKSTA "torturepacwksta" +#define TEST_MACHINE_NAME_S4U2SELF_BDC "tests4u2selfbdc" +#define TEST_MACHINE_NAME_S4U2SELF_WKSTA "tests4u2selfwk" +#define TEST_MACHINE_NAME_S4U2PROXY_WKSTA "tests4u2proxywk" + +struct pac_data { + DATA_BLOB pac_blob; + struct PAC_SIGNATURE_DATA *pac_srv_sig; + struct PAC_SIGNATURE_DATA *pac_kdc_sig; +}; + +/* A helper function which avoids touching the local databases to + * generate the session info, as we just want to verify the PAC + * details, not the full local token */ +static NTSTATUS test_generate_session_info_pac(struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_name, + const struct tsocket_address *remote_address, + uint32_t session_info_flags, + struct auth_session_info **session_info) +{ + NTSTATUS nt_status; + struct auth_user_info_dc *user_info_dc; + TALLOC_CTX *tmp_ctx; + struct pac_data *pac_data; + + if (pac_blob == NULL) { + DBG_ERR("pac_blob missing\n"); + return NT_STATUS_NO_IMPERSONATION_TOKEN; + } + + tmp_ctx = talloc_named(mem_ctx, 0, "gensec_gssapi_session_info context"); + NT_STATUS_HAVE_NO_MEMORY(tmp_ctx); + + auth_ctx->private_data = pac_data = talloc_zero(auth_ctx, struct pac_data); + + pac_data->pac_blob = data_blob_dup_talloc(pac_data, *pac_blob); + if (pac_data->pac_blob.length != pac_blob->length) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + pac_data->pac_srv_sig = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data->pac_srv_sig) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + pac_data->pac_kdc_sig = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data->pac_kdc_sig) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + nt_status = kerberos_pac_blob_to_user_info_dc(tmp_ctx, + *pac_blob, + smb_krb5_context->krb5_context, + &user_info_dc, + pac_data->pac_srv_sig, + pac_data->pac_kdc_sig); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + talloc_steal(pac_data, pac_data->pac_srv_sig); + talloc_steal(pac_data, pac_data->pac_kdc_sig); + + if (user_info_dc->info->authenticated) { + session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED; + } + + session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + nt_status = auth_generate_session_info(mem_ctx, + NULL, + NULL, + user_info_dc, session_info_flags, + session_info); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return nt_status; + } + + talloc_free(tmp_ctx); + return nt_status; +} + +/* Check to see if we can pass the PAC across to the NETLOGON server for validation */ + +static const struct PAC_BUFFER *get_pac_buffer(const struct PAC_DATA *pac_data, + enum PAC_TYPE type) +{ + const struct PAC_BUFFER *pac_buf = NULL; + uint32_t i; + + for (i = 0; i < pac_data->num_buffers; ++i) { + pac_buf = &pac_data->buffers[i]; + + if (pac_buf->type == type) { + break; + } + } + + return pac_buf; +} + +/* Also happens to be a really good one-step verfication of our Kerberos stack */ + +static bool netlogon_validate_pac(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *server_creds, + enum netr_SchannelType secure_channel_type, + const char *test_machine_name, + uint32_t negotiate_flags, + struct pac_data *pac_data, + struct auth_session_info *session_info); + +static bool test_PACVerify(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials, + enum netr_SchannelType secure_channel_type, + const char *test_machine_name, + uint32_t negotiate_flags) +{ + NTSTATUS status; + bool ok; + const char *pkinit_ccache = torture_setting_string(tctx, "pkinit_ccache", NULL); + bool pkinit_in_use = pkinit_ccache != NULL; + bool expect_pac_upn_dns_info = torture_setting_bool(tctx, "expect_pac_upn_dns_info", true); + size_t num_pac_buffers; + struct gensec_security *gensec_client_context; + struct gensec_security *gensec_server_context; + struct cli_credentials *client_creds; + struct cli_credentials *server_creds; + + DATA_BLOB client_to_server, server_to_client; + struct PAC_DATA pac_data_struct; + enum ndr_err_code ndr_err; + + struct auth4_context *auth_context; + struct auth_session_info *session_info; + struct pac_data *pac_data; + const struct PAC_BUFFER *pac_buf = NULL; + + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + torture_assert(tctx, tmp_ctx != NULL, "talloc_new() failed"); + + torture_comment(tctx, + "Testing PAC Verify (secure_channel_type: %d, machine: %s, negotiate_flags: 0x%08x\n", + secure_channel_type, test_machine_name, negotiate_flags); + + if (pkinit_in_use) { + struct cli_credentials *tmp_creds = NULL; + const char *error_string = NULL; + int rc; + + torture_comment(tctx, + "Using pkinit_ccache=%s\n", + pkinit_ccache); + + tmp_creds = cli_credentials_init(tctx); + torture_assert(tctx, tmp_creds, "Failed to create credentials"); + + rc = cli_credentials_set_ccache(tmp_creds, + tctx->lp_ctx, + pkinit_ccache, + CRED_SPECIFIED, + &error_string); + torture_assert_int_equal(tctx, + rc, + 0, + "cli_credentials_set_ccache failed"); + cli_credentials_set_kerberos_state(tmp_creds, + CRED_USE_KERBEROS_REQUIRED, + CRED_SPECIFIED); + + /* + * Copy the credentials in order to use a different MEMORY krb5 + * ccache for each client/server setup. The MEMORY cache + * identifier is a pointer to the creds container. If we copy + * it the pointer changes and we will get a new clean memory + * cache. + */ + client_creds = + cli_credentials_shallow_copy(tmp_ctx, tmp_creds); + torture_assert(tctx, + client_creds, + "Failed to copy of credentials"); + } else { + /* + * Copy the credentials in order to use a different MEMORY krb5 + * ccache for each client/server setup. The MEMORY cache + * identifier is a pointer to the creds container. If we copy + * it the pointer changes and we will get a new clean memory + * cache. + */ + client_creds = + cli_credentials_shallow_copy(tmp_ctx, + samba_cmdline_get_creds()); + torture_assert(tctx, + client_creds, + "Failed to copy of credentials"); + cli_credentials_invalidate_ccache(client_creds, CRED_SPECIFIED); + } + + + server_creds = cli_credentials_shallow_copy(tmp_ctx, + credentials); + torture_assert(tctx, server_creds, "Failed to copy of credentials"); + + auth_context = talloc_zero(tmp_ctx, struct auth4_context); + torture_assert(tctx, auth_context != NULL, "talloc_new() failed"); + + auth_context->generate_session_info_pac = test_generate_session_info_pac; + + status = gensec_client_start(tctx, &gensec_client_context, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "gensec_client_start (client) failed"); + + status = gensec_set_target_hostname(gensec_client_context, test_machine_name); + + status = gensec_set_credentials(gensec_client_context, client_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (client) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_client_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (client) failed"); + + status = gensec_server_start(tctx, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + auth_context, &gensec_server_context); + torture_assert_ntstatus_ok(tctx, status, "gensec_server_start (server) failed"); + + status = gensec_set_credentials(gensec_server_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (server) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_server_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (server) failed"); + + server_to_client = data_blob(NULL, 0); + + do { + /* Do a client-server update dance */ + status = gensec_update(gensec_client_context, tmp_ctx, server_to_client, &client_to_server); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (client) failed"); + } + + status = gensec_update(gensec_server_context, tmp_ctx, client_to_server, &server_to_client); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (server) failed"); + } + + if (NT_STATUS_IS_OK(status)) { + break; + } + } while (1); + + /* Extract the PAC using Samba's code */ + + status = gensec_session_info(gensec_server_context, gensec_server_context, &session_info); + torture_assert_ntstatus_ok(tctx, status, "gensec_session_info failed"); + + pac_data = talloc_get_type(auth_context->private_data, struct pac_data); + + torture_assert(tctx, pac_data != NULL, "gensec_update failed to fill in pac_data in auth_context"); + torture_assert(tctx, pac_data->pac_srv_sig != NULL, "pac_srv_sig not present"); + torture_assert(tctx, pac_data->pac_kdc_sig != NULL, "pac_kdc_sig not present"); + + ndr_err = ndr_pull_struct_blob(&pac_data->pac_blob, tmp_ctx, &pac_data_struct, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed"); + + num_pac_buffers = 6; + if (expect_pac_upn_dns_info) { + num_pac_buffers += 1; + } + if (pkinit_in_use) { + num_pac_buffers += 1; + } + + torture_assert_int_equal(tctx, pac_data_struct.version, 0, "version"); + torture_assert_int_equal(tctx, pac_data_struct.num_buffers, num_pac_buffers, "num_buffers"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_LOGON_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_LOGON_INFO"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_LOGON_INFO info"); + + if (pkinit_in_use) { + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_CREDENTIAL_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_CREDENTIAL_INFO"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_CREDENTIAL_INFO info"); + } + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_LOGON_NAME); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_LOGON_NAME"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_LOGON_NAME info"); + + if (expect_pac_upn_dns_info) { + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_UPN_DNS_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_UPN_DNS_INFO"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_UPN_DNS_INFO info"); + } + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_SRV_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_SRV_CHECKSUM"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_SRV_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_KDC_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_KDC_CHECKSUM"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_KDC_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_TICKET_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_TICKET_CHECKSUM"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_TICKET_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_FULL_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_FULL_CHECKSUM"); + torture_assert(tctx, + pac_buf->info != NULL, + "PAC_TYPE_FULL_CHECKSUM info"); + + ok = netlogon_validate_pac(tctx, p, server_creds, secure_channel_type, test_machine_name, + negotiate_flags, pac_data, session_info); + + talloc_free(tmp_ctx); + + return ok; +} + +static bool netlogon_validate_pac(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *server_creds, + enum netr_SchannelType secure_channel_type, + const char *test_machine_name, + uint32_t negotiate_flags, + struct pac_data *pac_data, + struct auth_session_info *session_info) +{ + struct PAC_Validate pac_wrapped_struct; + struct netlogon_creds_CredentialState *creds = NULL; + struct netr_Authenticator return_authenticator; + struct netr_Authenticator auth, auth2; + struct netr_GenericInfo generic; + struct netr_LogonSamLogon r; + union netr_Validation validation; + union netr_LogonLevel logon; + uint8_t authoritative; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + enum ndr_err_code ndr_err; + DATA_BLOB payload, pac_wrapped; + + if (!test_SetupCredentials2(p1, tctx, negotiate_flags, + server_creds, secure_channel_type, + &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, server_creds, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + pac_wrapped_struct.ChecksumLength = pac_data->pac_srv_sig->signature.length; + pac_wrapped_struct.SignatureType = pac_data->pac_kdc_sig->type; + pac_wrapped_struct.SignatureLength = pac_data->pac_kdc_sig->signature.length; + pac_wrapped_struct.ChecksumAndSignature = payload + = data_blob_talloc(tctx, NULL, + pac_wrapped_struct.ChecksumLength + + pac_wrapped_struct.SignatureLength); + memcpy(&payload.data[0], + pac_data->pac_srv_sig->signature.data, + pac_wrapped_struct.ChecksumLength); + memcpy(&payload.data[pac_wrapped_struct.ChecksumLength], + pac_data->pac_kdc_sig->signature.data, + pac_wrapped_struct.SignatureLength); + + ndr_err = ndr_push_struct_blob(&pac_wrapped, tctx, &pac_wrapped_struct, + (ndr_push_flags_fn_t)ndr_push_PAC_Validate); + torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_push_struct_blob of PACValidate structure failed"); + + torture_assert(tctx, (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR), "not willing to even try a PACValidate without RC4 encryption"); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, pac_wrapped.data, pac_wrapped.length); + } else { + netlogon_creds_arcfour_crypt(creds, pac_wrapped.data, pac_wrapped.length); + } + + generic.length = pac_wrapped.length; + generic.data = pac_wrapped.data; + + /* Validate it over the netlogon pipe */ + + generic.identity_info.parameter_control = 0; + generic.identity_info.logon_id = 0; + generic.identity_info.domain_name.string = session_info->info->domain_name; + generic.identity_info.account_name.string = session_info->info->account_name; + generic.identity_info.workstation.string = test_machine_name; + + generic.package_name.string = "Kerberos"; + + logon.generic = &generic; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon = &logon; + r.in.logon_level = NetlogonGenericInformation; + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.validation_level = NetlogonValidationGenericInfo2; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + r.out.return_authenticator = &return_authenticator; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogon failed"); + + /* This will break the signature nicely (even in the crypto wrapping), check we get a logon failure */ + generic.data[generic.length-1]++; + + logon.generic = &generic; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonGenericInformation; + r.in.logon = &logon; + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.validation_level = NetlogonValidationGenericInfo2; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_LOGON_FAILURE, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &r.out.return_authenticator->cred), + "Credential chaining failed"); + + /* This will break the parsing nicely (even in the crypto wrapping), check we get INVALID_PARAMETER */ + generic.length--; + + logon.generic = &generic; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonGenericInformation; + r.in.logon = &logon; + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.validation_level = NetlogonValidationGenericInfo2; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_PARAMETER, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + + pac_wrapped_struct.ChecksumLength = pac_data->pac_srv_sig->signature.length; + pac_wrapped_struct.SignatureType = pac_data->pac_kdc_sig->type; + + /* Break the SignatureType */ + pac_wrapped_struct.SignatureType++; + + pac_wrapped_struct.SignatureLength = pac_data->pac_kdc_sig->signature.length; + pac_wrapped_struct.ChecksumAndSignature = payload + = data_blob_talloc(tctx, NULL, + pac_wrapped_struct.ChecksumLength + + pac_wrapped_struct.SignatureLength); + memcpy(&payload.data[0], + pac_data->pac_srv_sig->signature.data, + pac_wrapped_struct.ChecksumLength); + memcpy(&payload.data[pac_wrapped_struct.ChecksumLength], + pac_data->pac_kdc_sig->signature.data, + pac_wrapped_struct.SignatureLength); + + ndr_err = ndr_push_struct_blob(&pac_wrapped, tctx, &pac_wrapped_struct, + (ndr_push_flags_fn_t)ndr_push_PAC_Validate); + torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_push_struct_blob of PACValidate structure failed"); + + torture_assert(tctx, (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR), "not willing to even try a PACValidate without RC4 encryption"); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, pac_wrapped.data, pac_wrapped.length); + } else { + netlogon_creds_arcfour_crypt(creds, pac_wrapped.data, pac_wrapped.length); + } + + generic.length = pac_wrapped.length; + generic.data = pac_wrapped.data; + + logon.generic = &generic; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonGenericInformation; + r.in.logon = &logon; + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.validation_level = NetlogonValidationGenericInfo2; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_LOGON_FAILURE, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &r.out.return_authenticator->cred), + "Credential chaining failed"); + + pac_wrapped_struct.ChecksumLength = pac_data->pac_srv_sig->signature.length; + pac_wrapped_struct.SignatureType = pac_data->pac_kdc_sig->type; + pac_wrapped_struct.SignatureLength = pac_data->pac_kdc_sig->signature.length; + + pac_wrapped_struct.ChecksumAndSignature = payload + = data_blob_talloc(tctx, NULL, + pac_wrapped_struct.ChecksumLength + + pac_wrapped_struct.SignatureLength); + memcpy(&payload.data[0], + pac_data->pac_srv_sig->signature.data, + pac_wrapped_struct.ChecksumLength); + memcpy(&payload.data[pac_wrapped_struct.ChecksumLength], + pac_data->pac_kdc_sig->signature.data, + pac_wrapped_struct.SignatureLength); + + /* Break the signature length */ + pac_wrapped_struct.SignatureLength++; + + ndr_err = ndr_push_struct_blob(&pac_wrapped, tctx, &pac_wrapped_struct, + (ndr_push_flags_fn_t)ndr_push_PAC_Validate); + torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_push_struct_blob of PACValidate structure failed"); + + torture_assert(tctx, (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR), "not willing to even try a PACValidate without RC4 encryption"); + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, pac_wrapped.data, pac_wrapped.length); + } else { + netlogon_creds_arcfour_crypt(creds, pac_wrapped.data, pac_wrapped.length); + } + + generic.length = pac_wrapped.length; + generic.data = pac_wrapped.data; + + logon.generic = &generic; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonGenericInformation; + r.in.logon = &logon; + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.validation_level = NetlogonValidationGenericInfo2; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogon_r(b, tctx, &r), + "LogonSamLogon failed"); + + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_INVALID_PARAMETER, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, &r.out.return_authenticator->cred), + "Credential chaining failed"); + + return true; +} + +static bool test_PACVerify_bdc_arcfour(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_PACVerify(tctx, p, credentials, SEC_CHAN_BDC, + TEST_MACHINE_NAME_BDC, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_PACVerify_bdc_aes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_PACVerify(tctx, p, credentials, SEC_CHAN_BDC, + TEST_MACHINE_NAME_BDC, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +static bool test_PACVerify_workstation_arcfour(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_PACVerify(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_PACVerify_workstation_aes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_PACVerify(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +#ifdef SAMBA4_USES_HEIMDAL +static NTSTATUS check_primary_group_in_validation(TALLOC_CTX *mem_ctx, + uint16_t validation_level, + const union netr_Validation *validation) +{ + const struct netr_SamBaseInfo *base = NULL; + int i; + switch (validation_level) { + case 2: + if (!validation || !validation->sam2) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam2->base; + break; + case 3: + if (!validation || !validation->sam3) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam3->base; + break; + case 6: + if (!validation || !validation->sam6) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam6->base; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + for (i = 0; i < base->groups.count; i++) { + if (base->groups.rids[i].rid == base->primary_gid) { + return NT_STATUS_OK; + } + } + return NT_STATUS_INVALID_PARAMETER; +} + +/* Check various ways to get the PAC, in particular check the group membership and + * other details between the PAC from a normal kinit, S4U2Self and a SamLogon */ +static bool test_S4U2Self(struct torture_context *tctx, + struct dcerpc_pipe *p1, + struct cli_credentials *credentials, + enum netr_SchannelType secure_channel_type, + const char *test_machine_name, + uint32_t negotiate_flags) +{ + NTSTATUS status; + struct dcerpc_pipe *p = NULL; + struct dcerpc_binding_handle *b = NULL; + + struct netr_LogonSamLogon r; + + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative; + + struct netr_Authenticator auth, auth2; + + DATA_BLOB client_to_server, server_to_client; + + struct netlogon_creds_CredentialState *creds; + struct gensec_security *gensec_client_context; + struct gensec_security *gensec_server_context; + struct cli_credentials *client_creds; + struct cli_credentials *server_creds; + + struct auth4_context *auth_context; + struct auth_session_info *kinit_session_info; + struct auth_session_info *s4u2self_session_info; + struct auth_user_info_dc *netlogon_user_info_dc; + + struct netr_NetworkInfo ninfo; + DATA_BLOB names_blob, chal, lm_resp, nt_resp; + size_t i; + int flags = CLI_CRED_NTLMv2_AUTH; + + struct dom_sid *builtin_domain; + + struct dom_sid *ai_auth_authority = NULL; + struct dom_sid *ai_service = NULL; + size_t ai_auth_authority_count = 0; + size_t ai_service_count = 0; + bool ok; + + TALLOC_CTX *tmp_ctx = talloc_new(tctx); + + torture_assert(tctx, tmp_ctx != NULL, "talloc_new() failed"); + + torture_comment(tctx, + "Testing S4U2SELF (secure_channel_type: %d, machine: %s, negotiate_flags: 0x%08x\n", + secure_channel_type, test_machine_name, negotiate_flags); + + /* + * Copy the credentials in order to use a different MEMORY krb5 ccache + * for each client/server setup. The MEMORY cache identifier is a + * pointer to the creds container. If we copy it the pointer changes and + * we will get a new clean memory cache. + */ + client_creds = cli_credentials_shallow_copy(tmp_ctx, + samba_cmdline_get_creds()); + torture_assert(tctx, client_creds, "Failed to copy of credentials"); + /* We use cli_credentials_get_ntlm_response(), so relax krb5 requirements. */ + cli_credentials_set_kerberos_state(client_creds, + CRED_USE_KERBEROS_DESIRED, + CRED_SPECIFIED); + + server_creds = cli_credentials_shallow_copy(tmp_ctx, + credentials); + torture_assert(tctx, server_creds, "Failed to copy of credentials"); + + if (!test_SetupCredentials2(p1, tctx, negotiate_flags, + server_creds, secure_channel_type, + &creds)) { + return false; + } + if (!test_SetupCredentialsPipe(p1, tctx, server_creds, creds, + DCERPC_SIGN | DCERPC_SEAL, &p)) { + return false; + } + b = p->binding_handle; + + auth_context = talloc_zero(tmp_ctx, struct auth4_context); + torture_assert(tctx, auth_context != NULL, "talloc_new() failed"); + + auth_context->generate_session_info_pac = test_generate_session_info_pac; + + /* First, do a normal Kerberos connection */ + + status = gensec_client_start(tctx, &gensec_client_context, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "gensec_client_start (client) failed"); + + status = gensec_set_target_hostname(gensec_client_context, test_machine_name); + + status = gensec_set_credentials(gensec_client_context, client_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (client) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_client_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (client) failed"); + + status = gensec_server_start(tctx, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + auth_context, &gensec_server_context); + torture_assert_ntstatus_ok(tctx, status, "gensec_server_start (server) failed"); + + status = gensec_set_credentials(gensec_server_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (server) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_server_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (server) failed"); + + server_to_client = data_blob(NULL, 0); + + do { + /* Do a client-server update dance */ + status = gensec_update(gensec_client_context, tmp_ctx, server_to_client, &client_to_server); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (client) failed"); + } + + status = gensec_update(gensec_server_context, tmp_ctx, client_to_server, &server_to_client); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (server) failed"); + } + + if (NT_STATUS_IS_OK(status)) { + break; + } + } while (1); + + /* Extract the PAC using Samba's code */ + + status = gensec_session_info(gensec_server_context, gensec_server_context, &kinit_session_info); + torture_assert_ntstatus_ok(tctx, status, "gensec_session_info failed"); + + + /* Now do the dance with S4U2Self */ + + /* Wipe out any existing ccache */ + cli_credentials_invalidate_ccache(client_creds, CRED_SPECIFIED); + cli_credentials_invalidate_ccache(server_creds, CRED_SPECIFIED); + cli_credentials_set_impersonate_principal(server_creds, + cli_credentials_get_principal(client_creds, tmp_ctx), + talloc_asprintf(tmp_ctx, "host/%s", test_machine_name)); + + status = gensec_client_start(tctx, &gensec_client_context, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "gensec_client_start (client) failed"); + + status = gensec_set_target_hostname(gensec_client_context, test_machine_name); + + /* We now set the same credentials on both client and server contexts */ + status = gensec_set_credentials(gensec_client_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (client) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_client_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (client) failed"); + + status = gensec_server_start(tctx, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + auth_context, &gensec_server_context); + torture_assert_ntstatus_ok(tctx, status, "gensec_server_start (server) failed"); + + status = gensec_set_credentials(gensec_server_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (server) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_server_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (server) failed"); + + server_to_client = data_blob(NULL, 0); + + do { + /* Do a client-server update dance */ + status = gensec_update(gensec_client_context, tmp_ctx, server_to_client, &client_to_server); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (client) failed"); + } + + status = gensec_update(gensec_server_context, tmp_ctx, client_to_server, &server_to_client); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (server) failed"); + } + + if (NT_STATUS_IS_OK(status)) { + break; + } + } while (1); + + /* Don't pollute the remaining tests with the changed credentials */ + cli_credentials_invalidate_ccache(server_creds, CRED_SPECIFIED); + cli_credentials_set_target_service(server_creds, NULL); + cli_credentials_set_impersonate_principal(server_creds, NULL, NULL); + + /* Extract the PAC using Samba's code */ + + status = gensec_session_info(gensec_server_context, gensec_server_context, &s4u2self_session_info); + torture_assert_ntstatus_ok(tctx, status, "gensec_session_info failed"); + + cli_credentials_get_ntlm_username_domain(client_creds, tctx, + &ninfo.identity_info.account_name.string, + &ninfo.identity_info.domain_name.string); + + /* Now try with SamLogon */ + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + chal = data_blob_const(ninfo.challenge, + sizeof(ninfo.challenge)); + + names_blob = NTLMv2_generate_names_blob(tctx, cli_credentials_get_workstation(server_creds), + cli_credentials_get_domain(server_creds)); + + status = cli_credentials_get_ntlm_response(client_creds, tctx, + &flags, + chal, + NULL, /* server_timestamp */ + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(tctx, status, "cli_credentials_get_ntlm_response failed"); + + ninfo.lm.data = lm_resp.data; + ninfo.lm.length = lm_resp.length; + + ninfo.nt.data = nt_resp.data; + ninfo.nt.length = nt_resp.length; + + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.workstation.string = cli_credentials_get_workstation(server_creds); + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(server_creds); + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonNetworkInformation; + r.in.logon = &logon; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + + r.in.validation_level = 3; + + status = dcerpc_netr_LogonSamLogon_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LogonSamLogon failed"); + + torture_assert(tctx, netlogon_creds_client_check(creds, + &r.out.return_authenticator->cred), + "Credential chaining failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogon failed"); + + status = make_user_info_dc_netlogon_validation(tmp_ctx, + ninfo.identity_info.account_name.string, + r.in.validation_level, + r.out.validation, + true, /* This user was authenticated */ + &netlogon_user_info_dc); + + torture_assert_ntstatus_ok(tctx, status, "make_user_info_dc_netlogon_validation failed"); + + /* Check that the primary group is present in validation's RID array */ + status = check_primary_group_in_validation(tmp_ctx, r.in.validation_level, r.out.validation); + torture_assert_ntstatus_ok(tctx, status, "check_primary_group_in_validation failed"); + + /* Check that the primary group is not duplicated in user_info_dc SID array */ + for (i = 2; i < netlogon_user_info_dc->num_sids; i++) { + torture_assert(tctx, !dom_sid_equal(&netlogon_user_info_dc->sids[1], + &netlogon_user_info_dc->sids[i]), + "Duplicate PrimaryGroupId in return SID array"); + } + + torture_assert_str_equal(tctx, netlogon_user_info_dc->info->account_name == NULL ? "" : netlogon_user_info_dc->info->account_name, + kinit_session_info->info->account_name, "Account name differs for kinit-based PAC"); + torture_assert_str_equal(tctx,netlogon_user_info_dc->info->account_name == NULL ? "" : netlogon_user_info_dc->info->account_name, + s4u2self_session_info->info->account_name, "Account name differs for S4U2Self"); + torture_assert_str_equal(tctx, netlogon_user_info_dc->info->full_name == NULL ? "" : netlogon_user_info_dc->info->full_name, kinit_session_info->info->full_name, "Full name differs for kinit-based PAC"); + torture_assert_str_equal(tctx, netlogon_user_info_dc->info->full_name == NULL ? "" : netlogon_user_info_dc->info->full_name, s4u2self_session_info->info->full_name, "Full name differs for S4U2Self"); + + builtin_domain = dom_sid_parse_talloc(tmp_ctx, SID_BUILTIN); + + /* KRB5 might have an additional sid, the asserted identity */ + ai_auth_authority = dom_sid_parse_talloc( + tmp_ctx, + SID_AUTHENTICATION_AUTHORITY_ASSERTED_IDENTITY); + + ai_service = dom_sid_parse_talloc( + tmp_ctx, + SID_SERVICE_ASSERTED_IDENTITY); + + ai_auth_authority_count = 0; + ai_service_count = 0; + for (i = 0; i < kinit_session_info->torture->num_dc_sids; i++) { + ok = dom_sid_equal(&kinit_session_info->torture->dc_sids[i], + ai_auth_authority); + if (ok) { + ai_auth_authority_count++; + } + + ok = dom_sid_equal(&kinit_session_info->torture->dc_sids[i], + ai_service); + if (ok) { + ai_service_count++; + } + } + + torture_assert_int_equal(tctx, ai_auth_authority_count, 1, + "Kinit authority asserted identity should be (1)"); + torture_assert_int_equal(tctx, ai_service_count, 0, + "Kinit service asserted identity should be (0)"); + + ai_auth_authority_count = 0; + ai_service_count = 0; + for (i = 0; i < s4u2self_session_info->torture->num_dc_sids; i++) { + ok = dom_sid_equal(&s4u2self_session_info->torture->dc_sids[i], + ai_auth_authority); + if (ok) { + ai_auth_authority_count++; + } + + ok = dom_sid_equal(&s4u2self_session_info->torture->dc_sids[i], + ai_service); + if (ok) { + ai_service_count++; + } + } + + torture_assert_int_equal(tctx, ai_auth_authority_count, 0, + "S4U2Self authority asserted identity should be (0)"); + torture_assert_int_equal(tctx, ai_service_count, 1, + "S4U2Self service asserted identity should be (1)"); + + torture_assert_int_equal(tctx, netlogon_user_info_dc->num_sids, kinit_session_info->torture->num_dc_sids - 1, "Different numbers of domain groups for kinit-based PAC"); + torture_assert_int_equal(tctx, netlogon_user_info_dc->num_sids, s4u2self_session_info->torture->num_dc_sids - 1, "Different numbers of domain groups for S4U2Self"); + + for (i = 0; i < netlogon_user_info_dc->num_sids; i++) { + torture_assert(tctx, dom_sid_equal(&netlogon_user_info_dc->sids[i], &kinit_session_info->torture->dc_sids[i]), "Different domain groups for kinit-based PAC"); + torture_assert(tctx, dom_sid_equal(&netlogon_user_info_dc->sids[i], &s4u2self_session_info->torture->dc_sids[i]), "Different domain groups for S4U2Self"); + torture_assert(tctx, !dom_sid_in_domain(builtin_domain, &s4u2self_session_info->torture->dc_sids[i]), "Returned BUILTIN domain in groups for S4U2Self"); + torture_assert(tctx, !dom_sid_in_domain(builtin_domain, &kinit_session_info->torture->dc_sids[i]), "Returned BUILTIN domain in groups kinit-based PAC"); + torture_assert(tctx, !dom_sid_in_domain(builtin_domain, &netlogon_user_info_dc->sids[i]), "Returned BUILTIN domian in groups from NETLOGON SamLogon reply"); + } + + return true; +} + +static bool test_S4U2Self_bdc_arcfour(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_S4U2Self(tctx, p, credentials, SEC_CHAN_BDC, + TEST_MACHINE_NAME_S4U2SELF_BDC, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_S4U2Self_bdc_aes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_S4U2Self(tctx, p, credentials, SEC_CHAN_BDC, + TEST_MACHINE_NAME_S4U2SELF_BDC, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +static bool test_S4U2Self_workstation_arcfour(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_S4U2Self(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_S4U2SELF_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_S4U2Self_workstation_aes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials) +{ + return test_S4U2Self(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_S4U2SELF_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} + +static bool test_S4U2Proxy(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials, + enum netr_SchannelType secure_channel_type, + const char *test_machine_name, + uint32_t negotiate_flags) +{ + NTSTATUS status; + struct gensec_security *gensec_client_context = NULL; + struct gensec_security *gensec_server_context = NULL; + struct cli_credentials *server_creds = NULL; + size_t num_pac_buffers; + struct auth4_context *auth_context = NULL; + struct auth_session_info *session_info = NULL; + struct pac_data *pac_data = NULL; + const struct PAC_BUFFER *pac_buf = NULL; + char *impersonate_princ = NULL, *self_princ = NULL, *target_princ = NULL; + enum ndr_err_code ndr_err; + struct PAC_DATA pac_data_struct; + struct PAC_CONSTRAINED_DELEGATION *deleg = NULL; + + DATA_BLOB client_to_server, server_to_client; + + auth_context = talloc_zero(tctx, struct auth4_context); + torture_assert_not_null(tctx, auth_context, "talloc_new() failed"); + + auth_context->generate_session_info_pac = test_generate_session_info_pac; + + torture_comment(tctx, + "Testing S4U2Proxy (secure_channel_type: %d, machine: %s, negotiate_flags: 0x%08x\n", + secure_channel_type, test_machine_name, negotiate_flags); + + impersonate_princ = cli_credentials_get_principal(samba_cmdline_get_creds(), tctx); + torture_assert_not_null(tctx, impersonate_princ, "Failed to get impersonate client name"); + + server_creds = cli_credentials_shallow_copy(tctx, credentials); + torture_assert_not_null(tctx, server_creds, "Failed to copy of credentials"); + + self_princ = talloc_asprintf(tctx, "host/%s", test_machine_name); + cli_credentials_invalidate_ccache(server_creds, CRED_SPECIFIED); + cli_credentials_set_impersonate_principal(server_creds, impersonate_princ, self_princ); + + /* Trigger S4U2Proxy by setting a target_service different than self_principal */ + target_princ = talloc_asprintf(tctx, "%s$", test_machine_name); + cli_credentials_set_target_service(server_creds, target_princ); + + status = gensec_client_start(tctx, &gensec_client_context, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, "gensec_client_start (client) failed"); + + status = gensec_set_target_principal(gensec_client_context, target_princ); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_target_hostname (client) failed"); + + /* We now set the same credentials on both client and server contexts */ + status = gensec_set_credentials(gensec_client_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (client) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_client_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (client) failed"); + + status = gensec_server_start(tctx, + lpcfg_gensec_settings(tctx, tctx->lp_ctx), + auth_context, &gensec_server_context); + torture_assert_ntstatus_ok(tctx, status, "gensec_server_start (server) failed"); + + status = gensec_set_credentials(gensec_server_context, server_creds); + torture_assert_ntstatus_ok(tctx, status, "gensec_set_credentials (server) failed"); + + status = gensec_start_mech_by_sasl_name(gensec_server_context, "GSSAPI"); + torture_assert_ntstatus_ok(tctx, status, "gensec_start_mech_by_sasl_name (server) failed"); + + server_to_client = data_blob(NULL, 0); + + do { + /* Do a client-server update dance */ + status = gensec_update(gensec_client_context, tctx, server_to_client, &client_to_server); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (client) failed"); + } + + status = gensec_update(gensec_server_context, tctx, client_to_server, &server_to_client); + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {; + torture_assert_ntstatus_ok(tctx, status, "gensec_update (server) failed"); + } + + if (NT_STATUS_IS_OK(status)) { + break; + } + } while (1); + + /* Extract the PAC using Samba's code */ + + status = gensec_session_info(gensec_server_context, gensec_server_context, &session_info); + torture_assert_ntstatus_ok(tctx, status, "gensec_session_info failed"); + + pac_data = talloc_get_type(auth_context->private_data, struct pac_data); + + torture_assert_not_null(tctx, pac_data, "gensec_update failed to fill in pac_data in auth_context"); + torture_assert_not_null(tctx, pac_data->pac_srv_sig, "pac_srv_sig not present"); + torture_assert_not_null(tctx, pac_data->pac_kdc_sig, "pac_kdc_sig not present"); + + ndr_err = ndr_pull_struct_blob(&pac_data->pac_blob, tctx, &pac_data_struct, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + torture_assert(tctx, NDR_ERR_CODE_IS_SUCCESS(ndr_err), "ndr_pull_struct_blob of PAC_DATA structure failed"); + + num_pac_buffers = 8; + + torture_assert_int_equal(tctx, pac_data_struct.version, 0, "version"); + torture_assert_int_equal(tctx, pac_data_struct.num_buffers, num_pac_buffers, "num_buffers"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_LOGON_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_LOGON_INFO"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_LOGON_INFO info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_LOGON_NAME); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_LOGON_NAME"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_LOGON_NAME info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_UPN_DNS_INFO); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_UPN_DNS_INFO"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_UPN_DNS_INFO info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_SRV_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_SRV_CHECKSUM"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_SRV_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_KDC_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_KDC_CHECKSUM"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_KDC_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_TICKET_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_TICKET_CHECKSUM"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_TICKET_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_FULL_CHECKSUM); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_FULL_CHECKSUM"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_FULL_CHECKSUM info"); + + pac_buf = get_pac_buffer(&pac_data_struct, PAC_TYPE_CONSTRAINED_DELEGATION); + torture_assert_not_null(tctx, pac_buf, "PAC_TYPE_CONSTRAINED_DELEGATION"); + torture_assert_not_null(tctx, pac_buf->info, "PAC_TYPE_CONSTRAINED_DELEGATION info"); + + deleg = pac_buf->info->constrained_delegation.info; + torture_assert_str_equal(tctx, deleg->proxy_target.string, target_princ, "wrong proxy_target"); + torture_assert_int_equal(tctx, deleg->num_transited_services, 1, "wrong transited_services number"); + torture_assert_str_equal(tctx, deleg->transited_services[0].string, + talloc_asprintf(tctx, "%s@%s", self_princ, cli_credentials_get_realm(credentials)), + "wrong transited_services[0]"); + + return netlogon_validate_pac(tctx, p, server_creds, secure_channel_type, test_machine_name, + negotiate_flags, pac_data, session_info); +} + +static bool setup_constrained_delegation(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct test_join *join_ctx, + const char *machine_name) +{ + struct samr_SetUserInfo r; + union samr_UserInfo user_info; + struct dcerpc_pipe *samr_pipe = torture_join_samr_pipe(join_ctx); + const char *server_dn_str = NULL; + struct ldb_context *sam_ctx = NULL; + struct ldb_dn *server_dn = NULL; + struct ldb_message *msg = NULL; + char *url = NULL; + int ret; + + url = talloc_asprintf(tctx, "ldap://%s", dcerpc_server_name(p)); + sam_ctx = ldb_wrap_connect(tctx, tctx->ev, tctx->lp_ctx, url, NULL, samba_cmdline_get_creds(), 0); + torture_assert_not_null(tctx, sam_ctx, "Connection to the SAMDB on DC failed!"); + + server_dn_str = samdb_search_string(sam_ctx, tctx, ldb_get_default_basedn(sam_ctx), "distinguishedName", + "samaccountname=%s$", machine_name); + torture_assert_not_null(tctx, server_dn_str, "samdb_search_string()"); + + server_dn = ldb_dn_new(tctx, sam_ctx, server_dn_str); + torture_assert_not_null(tctx, server_dn, "ldb_dn_new()"); + + msg = ldb_msg_new(tctx); + torture_assert_not_null(tctx, msg, "ldb_msg_new()"); + + msg->dn = server_dn; + ret = ldb_msg_add_string(msg, "msDS-AllowedToDelegateTo", talloc_asprintf(tctx, "%s$", machine_name)); + torture_assert_int_equal(tctx, ret, 0, "ldb_msg_add_string())"); + + ret = ldb_modify(sam_ctx, msg); + torture_assert_int_equal(tctx, ret, 0, "ldb_modify()"); + + /* Allow forwardable flag in S4U2Self */ + user_info.info16.acct_flags = ACB_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION | ACB_WSTRUST; + r.in.user_handle = torture_join_samr_user_policy(join_ctx); + r.in.level = 16; + r.in.info = &user_info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(samr_pipe->binding_handle, tctx, &r), + "failed to set ACB_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION info account flags"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to set ACB_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION into account flags"); + + return true; +} + +static bool test_S4U2Proxy_workstation_arcfour(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials, + struct test_join *join_ctx) +{ + torture_assert(tctx, setup_constrained_delegation(tctx, p, join_ctx, + TEST_MACHINE_NAME_S4U2PROXY_WKSTA), + "setup_constrained_delegation() failed"); + return test_S4U2Proxy(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_S4U2PROXY_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS); +} + +static bool test_S4U2Proxy_workstation_aes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *credentials, + struct test_join *join_ctx) +{ + torture_assert(tctx, setup_constrained_delegation(tctx, p, join_ctx, + TEST_MACHINE_NAME_S4U2PROXY_WKSTA), + "setup_constrained_delegation() failed"); + return test_S4U2Proxy(tctx, p, credentials, SEC_CHAN_WKSTA, + TEST_MACHINE_NAME_S4U2PROXY_WKSTA, + NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES); +} +#endif + +struct torture_suite *torture_rpc_remote_pac(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "pac"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netr-bdc-arcfour", + &ndr_table_netlogon, TEST_MACHINE_NAME_BDC); + torture_rpc_tcase_add_test_creds(tcase, "verify-sig-arcfour", test_PACVerify_bdc_arcfour); + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netr-bdc-aes", + &ndr_table_netlogon, TEST_MACHINE_NAME_BDC); + torture_rpc_tcase_add_test_creds(tcase, "verify-sig-aes", test_PACVerify_bdc_aes); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-arcfour", + &ndr_table_netlogon, TEST_MACHINE_NAME_WKSTA); + torture_rpc_tcase_add_test_creds(tcase, "verify-sig-arcfour", test_PACVerify_workstation_arcfour); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-aes", + &ndr_table_netlogon, TEST_MACHINE_NAME_WKSTA); + torture_rpc_tcase_add_test_creds(tcase, "verify-sig-aes", test_PACVerify_workstation_aes); + +#ifdef SAMBA4_USES_HEIMDAL + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netr-bdc-arcfour", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2SELF_BDC); + torture_rpc_tcase_add_test_creds(tcase, "s4u2self-arcfour", test_S4U2Self_bdc_arcfour); + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "netr-bcd-aes", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2SELF_BDC); + torture_rpc_tcase_add_test_creds(tcase, "s4u2self-aes", test_S4U2Self_bdc_aes); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-arcfour", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2SELF_WKSTA); + torture_rpc_tcase_add_test_creds(tcase, "s4u2self-arcfour", test_S4U2Self_workstation_arcfour); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-aes", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2SELF_WKSTA); + torture_rpc_tcase_add_test_creds(tcase, "s4u2self-aes", test_S4U2Self_workstation_aes); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-arcfour", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2PROXY_WKSTA); + torture_rpc_tcase_add_test_join(tcase, "s4u2proxy-arcfour", test_S4U2Proxy_workstation_arcfour); + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "netr-mem-aes", + &ndr_table_netlogon, TEST_MACHINE_NAME_S4U2PROXY_WKSTA); + torture_rpc_tcase_add_test_join(tcase, "s4u2proxy-aes", test_S4U2Proxy_workstation_aes); +#endif + return suite; +} diff --git a/source4/torture/rpc/rpc.c b/source4/torture/rpc/rpc.c new file mode 100644 index 0000000..773e564 --- /dev/null +++ b/source4/torture/rpc/rpc.c @@ -0,0 +1,663 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Jelmer Vernooij 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 "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "torture/smbtorture.h" +#include "librpc/ndr/ndr_table.h" +#include "../lib/util/dlinklist.h" + +static bool torture_rpc_teardown (struct torture_context *tcase, + void *data) +{ + struct torture_rpc_tcase_data *tcase_data = + (struct torture_rpc_tcase_data *)data; + if (tcase_data->join_ctx != NULL) + torture_leave_domain(tcase, tcase_data->join_ctx); + talloc_free(tcase_data); + return true; +} + +/** + * Obtain the DCE/RPC binding context associated with a torture context. + * + * @param tctx Torture context + * @param binding Pointer to store DCE/RPC binding + */ +NTSTATUS torture_rpc_binding(struct torture_context *tctx, + struct dcerpc_binding **binding) +{ + NTSTATUS status; + const char *binding_string = torture_setting_string(tctx, "binding", + NULL); + + if (binding_string == NULL) { + torture_comment(tctx, + "You must specify a DCE/RPC binding string\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + status = dcerpc_parse_binding(tctx, binding_string, binding); + if (NT_STATUS_IS_ERR(status)) { + torture_comment(tctx, + "Failed to parse dcerpc binding '%s'\n", + binding_string); + return status; + } + + return NT_STATUS_OK; +} + +/** + * open a rpc connection to the chosen binding string + */ +_PUBLIC_ NTSTATUS torture_rpc_connection(struct torture_context *tctx, + struct dcerpc_pipe **p, + const struct ndr_interface_table *table) +{ + NTSTATUS status; + struct dcerpc_binding *binding; + + status = torture_rpc_binding(tctx, &binding); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + return torture_rpc_connection_with_binding(tctx, binding, p, table); +} + +/** + * open a rpc connection to the chosen binding string + */ +_PUBLIC_ NTSTATUS torture_rpc_connection_with_binding(struct torture_context *tctx, + struct dcerpc_binding *binding, + struct dcerpc_pipe **p, + const struct ndr_interface_table *table) +{ + NTSTATUS status; + + dcerpc_init(); + + status = dcerpc_pipe_connect_b(tctx, + p, binding, table, + samba_cmdline_get_creds(), + tctx->ev, tctx->lp_ctx); + + if (NT_STATUS_IS_ERR(status)) { + torture_warning(tctx, "Failed to connect to remote server: %s %s\n", + dcerpc_binding_string(tctx, binding), nt_errstr(status)); + } + + return status; +} + +/** + * open a rpc connection to a specific transport + */ +NTSTATUS torture_rpc_connection_transport(struct torture_context *tctx, + struct dcerpc_pipe **p, + const struct ndr_interface_table *table, + enum dcerpc_transport_t transport, + uint32_t assoc_group_id, + uint32_t extra_flags) +{ + NTSTATUS status; + struct dcerpc_binding *binding; + + *p = NULL; + + status = torture_rpc_binding(tctx, &binding); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_binding_set_transport(binding, transport); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_binding_set_assoc_group_id(binding, assoc_group_id); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_binding_set_flags(binding, extra_flags, 0); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_pipe_connect_b(tctx, p, binding, table, + samba_cmdline_get_creds(), + tctx->ev, tctx->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + *p = NULL; + return status; + } + + return NT_STATUS_OK; +} + +static bool torture_rpc_setup_machine_workstation(struct torture_context *tctx, + void **data) +{ + NTSTATUS status; + struct dcerpc_binding *binding; + struct torture_rpc_tcase *tcase = talloc_get_type(tctx->active_tcase, + struct torture_rpc_tcase); + struct torture_rpc_tcase_data *tcase_data; + + status = torture_rpc_binding(tctx, &binding); + if (NT_STATUS_IS_ERR(status)) + return false; + + *data = tcase_data = talloc_zero(tctx, struct torture_rpc_tcase_data); + tcase_data->credentials = samba_cmdline_get_creds(); + tcase_data->join_ctx = torture_join_domain(tctx, tcase->machine_name, + ACB_WSTRUST, + &tcase_data->credentials); + if (tcase_data->join_ctx == NULL) + torture_fail(tctx, "Failed to join as WORKSTATION"); + + status = dcerpc_pipe_connect_b(tctx, + &(tcase_data->pipe), + binding, + tcase->table, + tcase_data->credentials, tctx->ev, tctx->lp_ctx); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + return NT_STATUS_IS_OK(status); +} + +static bool torture_rpc_setup_machine_bdc(struct torture_context *tctx, + void **data) +{ + NTSTATUS status; + struct dcerpc_binding *binding; + struct torture_rpc_tcase *tcase = talloc_get_type(tctx->active_tcase, + struct torture_rpc_tcase); + struct torture_rpc_tcase_data *tcase_data; + + status = torture_rpc_binding(tctx, &binding); + if (NT_STATUS_IS_ERR(status)) + return false; + + *data = tcase_data = talloc_zero(tctx, struct torture_rpc_tcase_data); + tcase_data->credentials = samba_cmdline_get_creds(); + tcase_data->join_ctx = torture_join_domain(tctx, tcase->machine_name, + ACB_SVRTRUST, + &tcase_data->credentials); + if (tcase_data->join_ctx == NULL) + torture_fail(tctx, "Failed to join as BDC"); + + status = dcerpc_pipe_connect_b(tctx, + &(tcase_data->pipe), + binding, + tcase->table, + tcase_data->credentials, tctx->ev, tctx->lp_ctx); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + return NT_STATUS_IS_OK(status); +} + +_PUBLIC_ struct torture_rpc_tcase *torture_suite_add_machine_workstation_rpc_iface_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + const char *machine_name) +{ + struct torture_rpc_tcase *tcase = talloc(suite, + struct torture_rpc_tcase); + + torture_suite_init_rpc_tcase(suite, tcase, name, table); + + tcase->machine_name = talloc_strdup(tcase, machine_name); + tcase->tcase.setup = torture_rpc_setup_machine_workstation; + tcase->tcase.teardown = torture_rpc_teardown; + + return tcase; +} + +_PUBLIC_ struct torture_rpc_tcase *torture_suite_add_machine_bdc_rpc_iface_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + const char *machine_name) +{ + struct torture_rpc_tcase *tcase = talloc(suite, + struct torture_rpc_tcase); + + torture_suite_init_rpc_tcase(suite, tcase, name, table); + + tcase->machine_name = talloc_strdup(tcase, machine_name); + tcase->tcase.setup = torture_rpc_setup_machine_bdc; + tcase->tcase.teardown = torture_rpc_teardown; + + return tcase; +} + +_PUBLIC_ bool torture_suite_init_rpc_tcase(struct torture_suite *suite, + struct torture_rpc_tcase *tcase, + const char *name, + const struct ndr_interface_table *table) +{ + if (!torture_suite_init_tcase(suite, (struct torture_tcase *)tcase, name)) + return false; + + tcase->table = table; + + return true; +} + +static bool torture_rpc_setup_anonymous(struct torture_context *tctx, + void **data) +{ + NTSTATUS status; + struct dcerpc_binding *binding; + struct torture_rpc_tcase_data *tcase_data; + struct torture_rpc_tcase *tcase = talloc_get_type(tctx->active_tcase, + struct torture_rpc_tcase); + + status = torture_rpc_binding(tctx, &binding); + if (NT_STATUS_IS_ERR(status)) + return false; + + *data = tcase_data = talloc_zero(tctx, struct torture_rpc_tcase_data); + tcase_data->credentials = cli_credentials_init_anon(tctx); + + status = dcerpc_pipe_connect_b(tctx, + &(tcase_data->pipe), + binding, + tcase->table, + tcase_data->credentials, tctx->ev, tctx->lp_ctx); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + return NT_STATUS_IS_OK(status); +} + +static bool torture_rpc_setup (struct torture_context *tctx, void **data) +{ + NTSTATUS status; + struct torture_rpc_tcase *tcase = talloc_get_type( + tctx->active_tcase, struct torture_rpc_tcase); + struct torture_rpc_tcase_data *tcase_data; + + *data = tcase_data = talloc_zero(tctx, struct torture_rpc_tcase_data); + tcase_data->credentials = samba_cmdline_get_creds(); + + status = torture_rpc_connection(tctx, + &(tcase_data->pipe), + tcase->table); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + return NT_STATUS_IS_OK(status); +} + + + +_PUBLIC_ struct torture_rpc_tcase *torture_suite_add_anon_rpc_iface_tcase(struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table) +{ + struct torture_rpc_tcase *tcase = talloc(suite, struct torture_rpc_tcase); + + torture_suite_init_rpc_tcase(suite, tcase, name, table); + + tcase->tcase.setup = torture_rpc_setup_anonymous; + tcase->tcase.teardown = torture_rpc_teardown; + + return tcase; +} + + +_PUBLIC_ struct torture_rpc_tcase *torture_suite_add_rpc_iface_tcase(struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table) +{ + struct torture_rpc_tcase *tcase = talloc(suite, struct torture_rpc_tcase); + + torture_suite_init_rpc_tcase(suite, tcase, name, table); + + tcase->tcase.setup = torture_rpc_setup; + tcase->tcase.teardown = torture_rpc_teardown; + + return tcase; +} + +static bool torture_rpc_wrap_test(struct torture_context *tctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct dcerpc_pipe *); + struct torture_rpc_tcase_data *tcase_data = + (struct torture_rpc_tcase_data *)tcase->data; + + fn = test->fn; + + return fn(tctx, tcase_data->pipe); +} + +static bool torture_rpc_wrap_test_ex(struct torture_context *tctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, const void *); + struct torture_rpc_tcase_data *tcase_data = + (struct torture_rpc_tcase_data *)tcase->data; + + fn = test->fn; + + return fn(tctx, tcase_data->pipe, test->data); +} + + +static bool torture_rpc_wrap_test_creds(struct torture_context *tctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, struct cli_credentials *); + struct torture_rpc_tcase_data *tcase_data = + (struct torture_rpc_tcase_data *)tcase->data; + + fn = test->fn; + + return fn(tctx, tcase_data->pipe, tcase_data->credentials); +} + +static bool torture_rpc_wrap_test_join(struct torture_context *tctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, struct cli_credentials *, struct test_join *); + struct torture_rpc_tcase_data *tcase_data = + (struct torture_rpc_tcase_data *)tcase->data; + + fn = test->fn; + + return fn(tctx, tcase_data->pipe, tcase_data->credentials, tcase_data->join_ctx); +} + +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *)) +{ + struct torture_test *test; + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = torture_rpc_wrap_test; + test->dangerous = false; + test->data = NULL; + test->fn = fn; + + DLIST_ADD_END(tcase->tcase.tests, test); + + return test; +} + +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test_creds( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, struct cli_credentials *)) +{ + struct torture_test *test; + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = torture_rpc_wrap_test_creds; + test->dangerous = false; + test->data = NULL; + test->fn = fn; + + DLIST_ADD_END(tcase->tcase.tests, test); + + return test; +} + +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test_join( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, + struct cli_credentials *, struct test_join *)) +{ + struct torture_test *test; + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = torture_rpc_wrap_test_join; + test->dangerous = false; + test->data = NULL; + test->fn = fn; + + DLIST_ADD_END(tcase->tcase.tests, test); + + return test; +} + +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test_ex( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, + void *), + void *userdata) +{ + struct torture_test *test; + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = torture_rpc_wrap_test_ex; + test->dangerous = false; + test->data = userdata; + test->fn = fn; + + DLIST_ADD_END(tcase->tcase.tests, test); + + return test; +} + +static bool torture_rpc_wrap_test_setup(struct torture_context *tctx, + struct torture_tcase *tcase, + struct torture_test *test) +{ + bool (*fn)(struct torture_context *, struct dcerpc_pipe *, const void *); + struct torture_rpc_tcase *rpc_tcase = talloc_get_type_abort( + tctx->active_tcase, struct torture_rpc_tcase); + struct torture_rpc_tcase_data *tcase_data = talloc_get_type_abort( + tcase->data, struct torture_rpc_tcase_data); + void *data = discard_const_p(void, test->data); + bool ok; + + ok = rpc_tcase->setup_fn(tctx, tcase_data->pipe, data); + if (!ok) { + return false; + } + + fn = test->fn; + + ok = fn(tctx, tcase_data->pipe, data); + if (!ok) { + return false; + } + + ok = rpc_tcase->teardown_fn(tctx, tcase_data->pipe, data); + if (!ok) { + return false; + } + + return true; +} + +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test_setup( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn)(struct torture_context *, + struct dcerpc_pipe *, + void *), + void *userdata) +{ + struct torture_test *test = NULL; + + test = talloc(tcase, struct torture_test); + + test->name = talloc_strdup(test, name); + test->description = NULL; + test->run = torture_rpc_wrap_test_setup; + test->dangerous = false; + test->data = userdata; + test->fn = fn; + + DLIST_ADD_END(tcase->tcase.tests, test); + + return test; +} + +_PUBLIC_ struct torture_rpc_tcase *torture_suite_add_rpc_setup_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + bool (*setup_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *), + bool (*teardown_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *)) +{ + struct torture_rpc_tcase *tcase = talloc( + suite, struct torture_rpc_tcase); + + torture_suite_init_rpc_tcase(suite, tcase, name, table); + + tcase->setup_fn = setup_fn; + tcase->teardown_fn = teardown_fn; + tcase->tcase.setup = torture_rpc_setup; + tcase->tcase.teardown = torture_rpc_teardown; + + return tcase; +} + +NTSTATUS torture_rpc_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "rpc"); + + ndr_table_init(); + + torture_suite_add_simple_test(suite, "lsa", torture_rpc_lsa); + torture_suite_add_simple_test(suite, "lsalookup", torture_rpc_lsa_lookup); + torture_suite_add_simple_test(suite, "lsa-getuser", torture_rpc_lsa_get_user); + torture_suite_add_suite(suite, torture_rpc_lsa_lookup_sids(suite)); + torture_suite_add_suite(suite, torture_rpc_lsa_lookup_names(suite)); + torture_suite_add_suite(suite, torture_rpc_lsa_secrets(suite)); + torture_suite_add_suite(suite, torture_rpc_lsa_trusted_domains(suite)); + torture_suite_add_suite(suite, torture_rpc_lsa_forest_trust(suite)); + torture_suite_add_suite(suite, torture_rpc_lsa_privileges(suite)); + torture_suite_add_suite(suite, torture_rpc_echo(suite)); + torture_suite_add_suite(suite, torture_rpc_dfs(suite)); + torture_suite_add_suite(suite, torture_rpc_frsapi(suite)); + torture_suite_add_suite(suite, torture_rpc_unixinfo(suite)); + torture_suite_add_suite(suite, torture_rpc_eventlog(suite)); + torture_suite_add_suite(suite, torture_rpc_atsvc(suite)); + torture_suite_add_suite(suite, torture_rpc_wkssvc(suite)); + torture_suite_add_suite(suite, torture_rpc_handles(suite)); + torture_suite_add_suite(suite, torture_rpc_object_uuid(suite)); + torture_suite_add_suite(suite, torture_rpc_winreg(suite)); + torture_suite_add_suite(suite, torture_rpc_spoolss(suite)); +#ifdef WITH_NTVFS_FILESERVER + torture_suite_add_suite(suite, torture_rpc_spoolss_notify(suite)); +#endif + torture_suite_add_suite(suite, torture_rpc_spoolss_win(suite)); + torture_suite_add_suite(suite, torture_rpc_spoolss_driver(suite)); + torture_suite_add_suite(suite, torture_rpc_spoolss_access(suite)); + torture_suite_add_suite(suite, torture_rpc_iremotewinspool(suite)); + torture_suite_add_suite(suite, torture_rpc_iremotewinspool_drv(suite)); + torture_suite_add_simple_test(suite, "samr", torture_rpc_samr); + torture_suite_add_simple_test(suite, "samr.users", torture_rpc_samr_users); + torture_suite_add_simple_test(suite, "samr.passwords.default", torture_rpc_samr_passwords); + torture_suite_add_suite(suite, torture_rpc_netlogon(suite)); + torture_suite_add_suite(suite, torture_rpc_netlogon_s3(suite)); + torture_suite_add_suite(suite, torture_rpc_netlogon_admin(suite)); + torture_suite_add_suite(suite, torture_rpc_netlogon_zerologon(suite)); + torture_suite_add_suite(suite, torture_rpc_netlogon_crypto_fips(suite)); + torture_suite_add_suite(suite, torture_rpc_remote_pac(suite)); + torture_suite_add_simple_test(suite, "samlogon", torture_rpc_samlogon); + torture_suite_add_simple_test(suite, "samsync", torture_rpc_samsync); + torture_suite_add_simple_test(suite, "schannel", torture_rpc_schannel); + torture_suite_add_simple_test(suite, "schannel2", torture_rpc_schannel2); + torture_suite_add_simple_test(suite, "bench-schannel1", torture_rpc_schannel_bench1); + torture_suite_add_simple_test(suite, "schannel_anon_setpw", torture_rpc_schannel_anon_setpw); + torture_suite_add_suite(suite, torture_rpc_srvsvc(suite)); + torture_suite_add_suite(suite, torture_rpc_svcctl(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_accessmask(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_handletype(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_workstation_auth(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_passwords_pwdlastset(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_passwords_badpwdcount(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_passwords_lockout(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_passwords_validate(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_user_privileges(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_large_dc(suite)); + torture_suite_add_suite(suite, torture_rpc_samr_priv(suite)); + torture_suite_add_suite(suite, torture_rpc_epmapper(suite)); + torture_suite_add_suite(suite, torture_rpc_initshutdown(suite)); + torture_suite_add_suite(suite, torture_rpc_oxidresolve(suite)); + torture_suite_add_suite(suite, torture_rpc_remact(suite)); + torture_suite_add_simple_test(suite, "mgmt", torture_rpc_mgmt); + torture_suite_add_simple_test(suite, "scanner", torture_rpc_scanner); + torture_suite_add_simple_test(suite, "countcalls", torture_rpc_countcalls); + torture_suite_add_simple_test(suite, "authcontext", torture_bind_authcontext); + torture_suite_add_suite(suite, torture_rpc_samba3(suite)); + torture_rpc_drsuapi_tcase(suite); + torture_rpc_drsuapi_w2k8_tcase(suite); + torture_rpc_drsuapi_cracknames_tcase(suite); + torture_suite_add_suite(suite, torture_rpc_dssetup(suite)); + torture_suite_add_suite(suite, torture_rpc_browser(suite)); + torture_suite_add_simple_test(suite, "altercontext", torture_rpc_alter_context); + torture_suite_add_simple_test(suite, "join", torture_rpc_join); + torture_drs_rpc_dsgetinfo_tcase(suite); + torture_suite_add_simple_test(suite, "bench-rpc", torture_bench_rpc); + torture_suite_add_simple_test(suite, "asyncbind", torture_async_bind); + torture_suite_add_suite(suite, torture_rpc_ntsvcs(suite)); + torture_suite_add_suite(suite, torture_rpc_bind(suite)); +#ifdef AD_DC_BUILD_IS_ENABLED + torture_suite_add_suite(suite, torture_rpc_backupkey(suite)); +#endif + torture_suite_add_suite(suite, torture_rpc_fsrvp(suite)); + torture_suite_add_suite(suite, torture_rpc_clusapi(suite)); + torture_suite_add_suite(suite, torture_rpc_witness(suite)); + torture_suite_add_suite(suite, torture_rpc_mdssvc(suite)); + + suite->description = talloc_strdup(suite, "DCE/RPC protocol and interface tests"); + + torture_register_suite(ctx, suite); + + return NT_STATUS_OK; +} diff --git a/source4/torture/rpc/samba3rpc.c b/source4/torture/rpc/samba3rpc.c new file mode 100644 index 0000000..36eabdc --- /dev/null +++ b/source4/torture/rpc/samba3rpc.c @@ -0,0 +1,4729 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc torture tests, designed to walk Samba3 code paths + + Copyright (C) Volker Lendecke 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 "libcli/raw/libcliraw.h" +#include "libcli/raw/raw_proto.h" +#include "torture/util.h" +#include "libcli/rap/rap.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_srvsvc_c.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_winreg_c.h" +#include "librpc/gen_ndr/ndr_wkssvc_c.h" +#include "librpc/gen_ndr/ndr_svcctl_c.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/libcli.h" +#include "libcli/smb_composite/smb_composite.h" +#include "libcli/auth/libcli_auth.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "lib/registry/registry.h" +#include "libcli/resolve/resolve.h" +#include "torture/ndr/ndr.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/smb/smbXcli_base.h" +#include "source3/rpc_client/init_samr.h" + +/* + * open pipe and bind, given an IPC$ context + */ + +static NTSTATUS pipe_bind_smb(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_tree *tree, + const char *pipe_name, + const struct ndr_interface_table *iface, + struct dcerpc_pipe **p) +{ + struct dcerpc_pipe *result; + NTSTATUS status; + + if (!(result = dcerpc_pipe_init(mem_ctx, tctx->ev))) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_pipe_open_smb(result, tree, pipe_name); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_pipe_open_smb failed: %s\n", + nt_errstr(status)); + talloc_free(result); + return status; + } + + status = dcerpc_bind_auth_none(result, iface); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_bind_auth_none failed: %s\n", nt_errstr(status)); + talloc_free(result); + return status; + } + + *p = result; + return NT_STATUS_OK; +} + +static NTSTATUS pipe_bind_smb2(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smb2_tree *tree, + const char *pipe_name, + const struct ndr_interface_table *iface, + struct dcerpc_pipe **p) +{ + struct dcerpc_pipe *result; + NTSTATUS status; + + if (!(result = dcerpc_pipe_init(mem_ctx, tctx->ev))) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_pipe_open_smb2(result, tree, pipe_name); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_pipe_open_smb2 failed: %s\n", + nt_errstr(status)); + talloc_free(result); + return status; + } + + status = dcerpc_bind_auth_none(result, iface); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_bind_auth_none failed: %s\n", nt_errstr(status)); + talloc_free(result); + return status; + } + + *p = result; + return NT_STATUS_OK; +} + +static NTSTATUS pipe_bind_smb_auth(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_tree *tree, + struct cli_credentials *creds, + uint8_t auth_type, + uint8_t auth_level, + const char *pipe_name, + const struct ndr_interface_table *iface, + struct dcerpc_pipe **p) +{ + struct dcerpc_pipe *result; + NTSTATUS status; + + if (!(result = dcerpc_pipe_init(mem_ctx, tctx->ev))) { + return NT_STATUS_NO_MEMORY; + } + + status = dcerpc_pipe_open_smb(result, tree, pipe_name); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_pipe_open_smb failed: %s\n", + nt_errstr(status)); + talloc_free(result); + return status; + } + + status = dcerpc_bind_auth(result, iface, creds, + lpcfg_gensec_settings(tctx->lp_ctx, tctx->lp_ctx), + auth_type, auth_level, NULL); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_bind_auth failed: %s\n", nt_errstr(status)); + talloc_free(result); + return status; + } + + *p = result; + return NT_STATUS_OK; +} + +/* + * This tests a RPC call using an invalid vuid + */ + +bool torture_bind_authcontext(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct lsa_ObjectAttribute objectattr; + struct lsa_OpenPolicy2 openpolicy; + struct policy_handle handle; + struct lsa_Close close_handle; + struct smbcli_session *tmp; + uint16_t tmp_vuid; + struct smbcli_session *session2; + struct smbcli_state *cli; + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct cli_credentials *anon_creds; + struct smb_composite_sesssetup setup; + struct smbcli_options options; + struct smbcli_session_options session_options; + + mem_ctx = talloc_init("torture_bind_authcontext"); + + if (mem_ctx == NULL) { + torture_comment(torture, "talloc_init failed\n"); + return false; + } + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(mem_ctx, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "smbcli_full_connection failed: %s\n", + nt_errstr(status)); + goto done; + } + + status = pipe_bind_smb(torture, mem_ctx, cli->tree, "\\lsarpc", + &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb failed"); + lsa_handle = lsa_pipe->binding_handle; + + openpolicy.in.system_name =talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(lsa_pipe)); + ZERO_STRUCT(objectattr); + openpolicy.in.attr = &objectattr; + openpolicy.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + openpolicy.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy2_r(lsa_handle, mem_ctx, &openpolicy); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "dcerpc_lsa_OpenPolicy2 failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(openpolicy.out.result)) { + torture_comment(torture, "dcerpc_lsa_OpenPolicy2 failed: %s\n", + nt_errstr(openpolicy.out.result)); + goto done; + } + + close_handle.in.handle = &handle; + close_handle.out.handle = &handle; + + status = dcerpc_lsa_Close_r(lsa_handle, mem_ctx, &close_handle); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "dcerpc_lsa_Close failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(close_handle.out.result)) { + torture_comment(torture, "dcerpc_lsa_Close failed: %s\n", + nt_errstr(close_handle.out.result)); + goto done; + } + + session2 = smbcli_session_init(cli->transport, mem_ctx, false, session_options); + if (session2 == NULL) { + torture_comment(torture, "smbcli_session_init failed\n"); + goto done; + } + + if (!(anon_creds = cli_credentials_init_anon(mem_ctx))) { + torture_comment(torture, "create_anon_creds failed\n"); + goto done; + } + + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = ""; + setup.in.credentials = anon_creds; + setup.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(session2, &setup); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "anon session setup failed: %s\n", + nt_errstr(status)); + goto done; + } + session2->vuid = setup.out.vuid; + + tmp = cli->tree->session; + tmp_vuid = smb1cli_session_current_id(tmp->smbXcli); + smb1cli_session_set_id(tmp->smbXcli, session2->vuid); + cli->tree->session = session2; + + status = dcerpc_lsa_OpenPolicy2_r(lsa_handle, mem_ctx, &openpolicy); + + torture_assert(torture, smbXcli_conn_is_connected(cli->transport->conn), + "smb still connected"); + torture_assert(torture, !dcerpc_binding_handle_is_connected(lsa_handle), + "dcerpc disonnected"); + + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) { + torture_comment(torture, "dcerpc_lsa_OpenPolicy2 with wrong vuid gave %s, " + "expected NT_STATUS_CONNECTION_DISCONNECTED\n", + nt_errstr(status)); + status = NT_STATUS_CONNECTION_DISCONNECTED; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR)) { + torture_comment(torture, "dcerpc_lsa_OpenPolicy2 with wrong vuid gave %s, " + "expected NT_STATUS_CONNECTION_DISCONNECTED\n", + nt_errstr(status)); + status = NT_STATUS_CONNECTION_DISCONNECTED; + } + + torture_assert_ntstatus_equal(torture, status, NT_STATUS_CONNECTION_DISCONNECTED, + "lsa connection disconnected"); + + smb1cli_session_set_id(tmp->smbXcli, tmp_vuid); + cli->tree->session = tmp; + talloc_free(lsa_pipe); + lsa_pipe = NULL; + + ret = true; + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Bind to lsa using a specific auth method + */ + +static bool bindtest(struct torture_context *tctx, + struct smbcli_state *cli, + struct cli_credentials *credentials, + uint8_t auth_type, uint8_t auth_level) +{ + TALLOC_CTX *mem_ctx; + bool ret = false; + NTSTATUS status; + + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_ObjectAttribute objectattr; + struct lsa_OpenPolicy2 openpolicy; + struct lsa_QueryInfoPolicy query; + union lsa_PolicyInformation *info = NULL; + struct policy_handle handle; + struct lsa_Close close_handle; + + if ((mem_ctx = talloc_init("bindtest")) == NULL) { + torture_comment(tctx, "talloc_init failed\n"); + return false; + } + + status = pipe_bind_smb_auth(tctx, mem_ctx, cli->tree, + credentials, auth_type, auth_level, + "\\lsarpc", &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "pipe_bind_smb_auth failed"); + lsa_handle = lsa_pipe->binding_handle; + + openpolicy.in.system_name =talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(lsa_pipe)); + ZERO_STRUCT(objectattr); + openpolicy.in.attr = &objectattr; + openpolicy.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + openpolicy.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy2_r(lsa_handle, mem_ctx, &openpolicy); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_lsa_OpenPolicy2 failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(openpolicy.out.result)) { + torture_comment(tctx, "dcerpc_lsa_OpenPolicy2 failed: %s\n", + nt_errstr(openpolicy.out.result)); + goto done; + } + + query.in.handle = &handle; + query.in.level = LSA_POLICY_INFO_DOMAIN; + query.out.info = &info; + + status = dcerpc_lsa_QueryInfoPolicy_r(lsa_handle, mem_ctx, &query); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_lsa_QueryInfoPolicy failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(query.out.result)) { + torture_comment(tctx, "dcerpc_lsa_QueryInfoPolicy failed: %s\n", + nt_errstr(query.out.result)); + goto done; + } + + close_handle.in.handle = &handle; + close_handle.out.handle = &handle; + + status = dcerpc_lsa_Close_r(lsa_handle, mem_ctx, &close_handle); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_lsa_Close failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(close_handle.out.result)) { + torture_comment(tctx, "dcerpc_lsa_Close failed: %s\n", + nt_errstr(close_handle.out.result)); + goto done; + } + + + ret = true; + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * test authenticated RPC binds with the variants Samba3 does support + */ + +static bool torture_bind_samba3(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_state *cli; + struct smbcli_options options; + struct smbcli_session_options session_options; + + mem_ctx = talloc_init("torture_bind_authcontext"); + + if (mem_ctx == NULL) { + torture_comment(torture, "talloc_init failed\n"); + return false; + } + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(mem_ctx, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "smbcli_full_connection failed: %s\n", + nt_errstr(status)); + goto done; + } + + ret = true; + + ret &= bindtest(torture, cli, samba_cmdline_get_creds(), + DCERPC_AUTH_TYPE_NTLMSSP, + DCERPC_AUTH_LEVEL_INTEGRITY); + ret &= bindtest(torture, cli, samba_cmdline_get_creds(), + DCERPC_AUTH_TYPE_NTLMSSP, + DCERPC_AUTH_LEVEL_PRIVACY); + ret &= bindtest(torture, cli, samba_cmdline_get_creds(), + DCERPC_AUTH_TYPE_SPNEGO, + DCERPC_AUTH_LEVEL_INTEGRITY); + ret &= bindtest(torture, cli, samba_cmdline_get_creds(), + DCERPC_AUTH_TYPE_SPNEGO, + DCERPC_AUTH_LEVEL_PRIVACY); + + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Lookup or create a user and return all necessary info + */ + +static bool get_usr_handle(struct torture_context *tctx, + struct smbcli_state *cli, + TALLOC_CTX *mem_ctx, + struct cli_credentials *admin_creds, + uint8_t auth_type, + uint8_t auth_level, + const char *username, + char **domain, + struct dcerpc_pipe **result_pipe, + struct policy_handle **result_handle, + struct dom_sid **sid_p) +{ + struct dcerpc_pipe *samr_pipe; + struct dcerpc_binding_handle *samr_handle; + NTSTATUS status; + struct policy_handle conn_handle; + struct policy_handle domain_handle; + struct policy_handle *user_handle; + struct samr_Connect2 conn; + struct samr_EnumDomains enumdom; + uint32_t resume_handle = 0; + uint32_t num_entries = 0; + struct samr_SamArray *sam = NULL; + struct samr_LookupDomain l; + struct dom_sid2 *sid = NULL; + int dom_idx; + struct lsa_String domain_name; + struct lsa_String user_name; + struct samr_OpenDomain o; + struct samr_CreateUser2 c; + uint32_t user_rid,access_granted; + + if (admin_creds != NULL) { + status = pipe_bind_smb_auth(tctx, mem_ctx, cli->tree, + admin_creds, auth_type, auth_level, + "\\samr", &ndr_table_samr, &samr_pipe); + torture_assert_ntstatus_ok(tctx, status, "pipe_bind_smb_auth failed"); + } else { + /* We must have an authenticated SMB connection */ + status = pipe_bind_smb(tctx, mem_ctx, cli->tree, + "\\samr", &ndr_table_samr, &samr_pipe); + torture_assert_ntstatus_ok(tctx, status, "pipe_bind_smb_auth failed"); + } +#if 0 + samr_pipe->conn->flags |= DCERPC_DEBUG_PRINT_IN | DCERPC_DEBUG_PRINT_OUT; +#endif + samr_handle = samr_pipe->binding_handle; + + conn.in.system_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(samr_pipe)); + conn.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + conn.out.connect_handle = &conn_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_Connect2_r(samr_handle, mem_ctx, &conn), + "samr_Connect2 failed"); + torture_assert_ntstatus_ok(tctx, conn.out.result, + "samr_Connect2 failed"); + + enumdom.in.connect_handle = &conn_handle; + enumdom.in.resume_handle = &resume_handle; + enumdom.in.buf_size = (uint32_t)-1; + enumdom.out.resume_handle = &resume_handle; + enumdom.out.num_entries = &num_entries; + enumdom.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_EnumDomains_r(samr_handle, mem_ctx, &enumdom), + "samr_EnumDomains failed"); + torture_assert_ntstatus_ok(tctx, enumdom.out.result, + "samr_EnumDomains failed"); + + torture_assert_int_equal(tctx, *enumdom.out.num_entries, 2, + "samr_EnumDomains returned unexpected num_entries"); + + dom_idx = strequal(sam->entries[0].name.string, + "builtin") ? 1:0; + + l.in.connect_handle = &conn_handle; + domain_name.string = sam->entries[dom_idx].name.string; + *domain = talloc_strdup(mem_ctx, domain_name.string); + l.in.domain_name = &domain_name; + l.out.sid = &sid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_LookupDomain_r(samr_handle, mem_ctx, &l), + "samr_LookupDomain failed"); + torture_assert_ntstatus_ok(tctx, l.out.result, + "samr_LookupDomain failed"); + + o.in.connect_handle = &conn_handle; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.in.sid = *l.out.sid; + o.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenDomain_r(samr_handle, mem_ctx, &o), + "samr_OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, o.out.result, + "samr_OpenDomain failed"); + + c.in.domain_handle = &domain_handle; + user_name.string = username; + c.in.account_name = &user_name; + c.in.acct_flags = ACB_NORMAL; + c.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + user_handle = talloc(mem_ctx, struct policy_handle); + c.out.user_handle = user_handle; + c.out.access_granted = &access_granted; + c.out.rid = &user_rid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_CreateUser2_r(samr_handle, mem_ctx, &c), + "samr_CreateUser2 failed"); + + if (NT_STATUS_EQUAL(c.out.result, NT_STATUS_USER_EXISTS)) { + struct samr_LookupNames ln; + struct samr_OpenUser ou; + struct samr_Ids rids, types; + + ln.in.domain_handle = &domain_handle; + ln.in.num_names = 1; + ln.in.names = &user_name; + ln.out.rids = &rids; + ln.out.types = &types; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_LookupNames_r(samr_handle, mem_ctx, &ln), + "samr_LookupNames failed"); + torture_assert_ntstatus_ok(tctx, ln.out.result, + "samr_LookupNames failed"); + + ou.in.domain_handle = &domain_handle; + ou.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + user_rid = ou.in.rid = ln.out.rids->ids[0]; + ou.out.user_handle = user_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenUser_r(samr_handle, mem_ctx, &ou), + "samr_OpenUser failed"); + status = ou.out.result; + } else { + status = c.out.result; + } + + torture_assert_ntstatus_ok(tctx, status, + "samr_CreateUser failed"); + + *result_pipe = samr_pipe; + *result_handle = user_handle; + if (sid_p != NULL) { + *sid_p = dom_sid_add_rid(mem_ctx, *l.out.sid, user_rid); + } + return true; + +} + +/* + * Create a test user + */ + +static bool create_user(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct smbcli_state *cli, + struct cli_credentials *admin_creds, + const char *username, const char *password, + char **domain_name, + struct dom_sid **user_sid) +{ + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + struct dcerpc_pipe *samr_pipe; + struct dcerpc_binding_handle *samr_handle; + struct policy_handle *wks_handle; + bool ret = false; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + torture_comment(tctx, "talloc_init failed\n"); + return false; + } + + ret = get_usr_handle(tctx, cli, tmp_ctx, admin_creds, + DCERPC_AUTH_TYPE_NTLMSSP, + DCERPC_AUTH_LEVEL_INTEGRITY, + username, domain_name, &samr_pipe, &wks_handle, + user_sid); + if (ret == false) { + torture_comment(tctx, "get_usr_handle failed\n"); + goto done; + } + samr_handle = samr_pipe->binding_handle; + + { + struct samr_SetUserInfo2 sui2; + struct samr_SetUserInfo sui; + struct samr_QueryUserInfo qui; + union samr_UserInfo u_info; + union samr_UserInfo *info; + DATA_BLOB session_key; + + ZERO_STRUCT(u_info); + encode_pw_buffer(u_info.info23.password.data, password, + STR_UNICODE); + + status = dcerpc_fetch_session_key(samr_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed\n"); + goto done; + } + + status = init_samr_CryptPassword(password, + &session_key, + &u_info.info23.password); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "init_samr_CryptPassword failed\n"); + goto done; + } + + u_info.info23.info.password_expired = 0; + u_info.info23.info.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT | + SAMR_FIELD_LM_PASSWORD_PRESENT | + SAMR_FIELD_EXPIRED_FLAG; + sui2.in.user_handle = wks_handle; + sui2.in.info = &u_info; + sui2.in.level = 23; + + status = dcerpc_samr_SetUserInfo2_r(samr_handle, tmp_ctx, &sui2); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "samr_SetUserInfo(23) failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(sui2.out.result)) { + torture_comment(tctx, "samr_SetUserInfo(23) failed: %s\n", + nt_errstr(sui2.out.result)); + goto done; + } + + u_info.info16.acct_flags = ACB_NORMAL; + sui.in.user_handle = wks_handle; + sui.in.info = &u_info; + sui.in.level = 16; + + status = dcerpc_samr_SetUserInfo_r(samr_handle, tmp_ctx, &sui); + if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(sui.out.result)) { + torture_comment(tctx, "samr_SetUserInfo(16) failed\n"); + goto done; + } + + qui.in.user_handle = wks_handle; + qui.in.level = 21; + qui.out.info = &info; + + status = dcerpc_samr_QueryUserInfo_r(samr_handle, tmp_ctx, &qui); + if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(qui.out.result)) { + torture_comment(tctx, "samr_QueryUserInfo(21) failed\n"); + goto done; + } + + info->info21.allow_password_change = 0; + info->info21.force_password_change = 0; + info->info21.account_name.string = NULL; + info->info21.rid = 0; + info->info21.acct_expiry = 0; + info->info21.fields_present = 0x81827fa; /* copy usrmgr.exe */ + + u_info.info21 = info->info21; + sui.in.user_handle = wks_handle; + sui.in.info = &u_info; + sui.in.level = 21; + + status = dcerpc_samr_SetUserInfo_r(samr_handle, tmp_ctx, &sui); + if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(sui.out.result)) { + torture_comment(tctx, "samr_SetUserInfo(21) failed\n"); + goto done; + } + } + + *domain_name= talloc_steal(mem_ctx, *domain_name); + *user_sid = talloc_steal(mem_ctx, *user_sid); + ret = true; + done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * Delete a test user + */ + +static bool delete_user(struct torture_context *tctx, + struct smbcli_state *cli, + struct cli_credentials *admin_creds, + const char *username) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + char *dom_name; + struct dcerpc_pipe *samr_pipe; + struct dcerpc_binding_handle *samr_handle; + struct policy_handle *user_handle; + bool ret = false; + + if ((mem_ctx = talloc_init("leave")) == NULL) { + torture_comment(tctx, "talloc_init failed\n"); + return false; + } + + ret = get_usr_handle(tctx, cli, mem_ctx, admin_creds, + DCERPC_AUTH_TYPE_NTLMSSP, + DCERPC_AUTH_LEVEL_INTEGRITY, + username, &dom_name, &samr_pipe, + &user_handle, NULL); + if (ret == false) { + torture_comment(tctx, "get_wks_handle failed\n"); + goto done; + } + samr_handle = samr_pipe->binding_handle; + + { + struct samr_DeleteUser d; + + d.in.user_handle = user_handle; + d.out.user_handle = user_handle; + + status = dcerpc_samr_DeleteUser_r(samr_handle, mem_ctx, &d); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "samr_DeleteUser failed %s\n", nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(d.out.result)) { + torture_comment(tctx, "samr_DeleteUser failed %s\n", nt_errstr(d.out.result)); + goto done; + } + + } + + ret = true; + + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Do a Samba3-style join + */ + +static bool join3(struct torture_context *tctx, + struct smbcli_state *cli, + bool use_level25, + struct cli_credentials *admin_creds, + struct cli_credentials *wks_creds) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + char *dom_name; + struct dcerpc_pipe *samr_pipe; + struct dcerpc_binding_handle *samr_handle; + struct policy_handle *wks_handle; + bool ret = false; + NTTIME last_password_change; + + if ((mem_ctx = talloc_init("join3")) == NULL) { + torture_comment(tctx, "talloc_init failed\n"); + return false; + } + + ret = get_usr_handle( + tctx, cli, mem_ctx, admin_creds, + DCERPC_AUTH_TYPE_NTLMSSP, + DCERPC_AUTH_LEVEL_PRIVACY, + talloc_asprintf(mem_ctx, "%s$", + cli_credentials_get_workstation(wks_creds)), + &dom_name, &samr_pipe, &wks_handle, NULL); + if (ret == false) { + torture_comment(tctx, "get_wks_handle failed\n"); + goto done; + } + samr_handle = samr_pipe->binding_handle; + ret = false; + { + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + + q.in.user_handle = wks_handle; + q.in.level = 21; + q.out.info = &info; + + status = dcerpc_samr_QueryUserInfo_r(samr_handle, mem_ctx, &q); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "QueryUserInfo failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_warning(tctx, "QueryUserInfo failed: %s\n", + nt_errstr(q.out.result)); + goto done; + } + + + last_password_change = info->info21.last_password_change; + } + + cli_credentials_set_domain(wks_creds, dom_name, CRED_SPECIFIED); + + if (use_level25) { + struct samr_SetUserInfo2 sui2; + union samr_UserInfo u_info; + struct samr_UserInfo21 *i21 = &u_info.info25.info; + DATA_BLOB session_key; + + ZERO_STRUCT(u_info); + + i21->full_name.string = talloc_asprintf( + mem_ctx, "%s$", + cli_credentials_get_workstation(wks_creds)); + i21->acct_flags = ACB_WSTRUST; + i21->fields_present = SAMR_FIELD_FULL_NAME | + SAMR_FIELD_ACCT_FLAGS | SAMR_FIELD_NT_PASSWORD_PRESENT; + /* this would break the test result expectations + i21->fields_present |= SAMR_FIELD_EXPIRED_FLAG; + i21->password_expired = 1; + */ + + status = dcerpc_fetch_session_key(samr_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed: %s\n", + nt_errstr(status)); + goto done; + } + + status = init_samr_CryptPasswordEx(cli_credentials_get_password(wks_creds), + &session_key, + &u_info.info25.password); + + sui2.in.user_handle = wks_handle; + sui2.in.level = 25; + sui2.in.info = &u_info; + + status = dcerpc_samr_SetUserInfo2_r(samr_handle, mem_ctx, &sui2); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "samr_SetUserInfo2(25) failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(sui2.out.result)) { + torture_comment(tctx, "samr_SetUserInfo2(25) failed: %s\n", + nt_errstr(sui2.out.result)); + goto done; + } + } else { + struct samr_SetUserInfo2 sui2; + struct samr_SetUserInfo sui; + union samr_UserInfo u_info; + DATA_BLOB session_key; + + encode_pw_buffer(u_info.info24.password.data, + cli_credentials_get_password(wks_creds), + STR_UNICODE); + /* just to make this test pass */ + u_info.info24.password_expired = 1; + + status = dcerpc_fetch_session_key(samr_pipe, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed\n"); + goto done; + } + + status = init_samr_CryptPassword(cli_credentials_get_password(wks_creds), + &session_key, + &u_info.info24.password); + + sui2.in.user_handle = wks_handle; + sui2.in.info = &u_info; + sui2.in.level = 24; + + status = dcerpc_samr_SetUserInfo2_r(samr_handle, mem_ctx, &sui2); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "samr_SetUserInfo(24) failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(sui2.out.result)) { + torture_comment(tctx, "samr_SetUserInfo(24) failed: %s\n", + nt_errstr(sui2.out.result)); + goto done; + } + + u_info.info16.acct_flags = ACB_WSTRUST; + sui.in.user_handle = wks_handle; + sui.in.info = &u_info; + sui.in.level = 16; + + status = dcerpc_samr_SetUserInfo_r(samr_handle, mem_ctx, &sui); + if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(sui.out.result)) { + torture_comment(tctx, "samr_SetUserInfo(16) failed\n"); + goto done; + } + } + + { + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + + q.in.user_handle = wks_handle; + q.in.level = 21; + q.out.info = &info; + + status = dcerpc_samr_QueryUserInfo_r(samr_handle, mem_ctx, &q); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "QueryUserInfo failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_warning(tctx, "QueryUserInfo failed: %s\n", + nt_errstr(q.out.result)); + goto done; + } + + if (use_level25) { + if (last_password_change + == info->info21.last_password_change) { + torture_warning(tctx, "last_password_change unchanged " + "during join, level25 must change " + "it\n"); + goto done; + } + } + else { + if (last_password_change + != info->info21.last_password_change) { + torture_warning(tctx, "last_password_change changed " + "during join, level24 doesn't " + "change it\n"); + goto done; + } + } + } + + ret = true; + + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Do a ReqChallenge/Auth2 and get the wks creds + */ + +static bool auth2(struct torture_context *tctx, + struct smbcli_state *cli, + struct cli_credentials *wks_cred) +{ + TALLOC_CTX *mem_ctx; + struct dcerpc_pipe *net_pipe; + struct dcerpc_binding_handle *net_handle; + bool result = false; + NTSTATUS status; + struct netr_ServerReqChallenge r; + struct netr_Credential netr_cli_creds; + struct netr_Credential netr_srv_creds; + uint32_t negotiate_flags; + struct netr_ServerAuthenticate2 a; + struct netlogon_creds_CredentialState *creds_state; + struct netr_Credential netr_cred; + struct samr_Password mach_pw; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + torture_comment(tctx, "talloc_new failed\n"); + return false; + } + + status = pipe_bind_smb(tctx, mem_ctx, cli->tree, "\\netlogon", + &ndr_table_netlogon, &net_pipe); + torture_assert_ntstatus_ok_goto(tctx, status, result, done, + "pipe_bind_smb failed"); + net_handle = net_pipe->binding_handle; + + r.in.computer_name = cli_credentials_get_workstation(wks_cred); + r.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + if (r.in.server_name == NULL) { + torture_comment(tctx, "talloc_asprintf failed\n"); + goto done; + } + generate_random_buffer(netr_cli_creds.data, + sizeof(netr_cli_creds.data)); + r.in.credentials = &netr_cli_creds; + r.out.return_credentials = &netr_srv_creds; + + status = dcerpc_netr_ServerReqChallenge_r(net_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "netr_ServerReqChallenge failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "netr_ServerReqChallenge failed: %s\n", + nt_errstr(r.out.result)); + goto done; + } + + negotiate_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS | NETLOGON_NEG_SUPPORTS_AES; + E_md4hash(cli_credentials_get_password(wks_cred), mach_pw.hash); + + a.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + a.in.account_name = talloc_asprintf( + mem_ctx, "%s$", cli_credentials_get_workstation(wks_cred)); + a.in.computer_name = cli_credentials_get_workstation(wks_cred); + a.in.secure_channel_type = SEC_CHAN_WKSTA; + a.in.negotiate_flags = &negotiate_flags; + a.out.negotiate_flags = &negotiate_flags; + a.in.credentials = &netr_cred; + a.out.return_credentials = &netr_cred; + + creds_state = netlogon_creds_client_init(mem_ctx, + a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + r.in.credentials, + r.out.return_credentials, &mach_pw, + &netr_cred, negotiate_flags); + torture_assert(tctx, (creds_state != NULL), "memory allocation failed"); + + status = dcerpc_netr_ServerAuthenticate2_r(net_handle, mem_ctx, &a); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "netr_ServerServerAuthenticate2 failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(a.out.result)) { + torture_comment(tctx, "netr_ServerServerAuthenticate2 failed: %s\n", + nt_errstr(a.out.result)); + goto done; + } + + if (!netlogon_creds_client_check(creds_state, a.out.return_credentials)) { + torture_comment(tctx, "creds_client_check failed\n"); + goto done; + } + + cli_credentials_set_netlogon_creds(wks_cred, creds_state); + + result = true; + + done: + talloc_free(mem_ctx); + return result; +} + +/* + * Do a couple of schannel protected Netlogon ops: Interactive and Network + * login, and change the wks password + */ + +static bool schan(struct torture_context *tctx, + struct smbcli_state *cli, + struct cli_credentials *wks_creds, + struct cli_credentials *user_creds) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct dcerpc_pipe *net_pipe; + struct dcerpc_binding_handle *net_handle; + int i; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + torture_comment(tctx, "talloc_new failed\n"); + return false; + } + +#if 1 + status = pipe_bind_smb_auth(tctx, mem_ctx, cli->tree, + wks_creds, + DCERPC_AUTH_TYPE_SCHANNEL, + DCERPC_AUTH_LEVEL_PRIVACY, + "\\netlogon", &ndr_table_netlogon, &net_pipe); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "pipe_bind_smb_auth failed"); + net_pipe->conn->flags |= (DCERPC_SIGN | DCERPC_SEAL); +#else + status = pipe_bind_smb(tctx, mem_ctx, cli->tree, + "\\netlogon", &ndr_table_netlogon, &net_pipe); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "pipe_bind_smb failed"); +#endif +#if 0 + net_pipe->conn->flags |= DCERPC_DEBUG_PRINT_IN | + DCERPC_DEBUG_PRINT_OUT; +#endif + net_handle = net_pipe->binding_handle; + + + for (i=2; i<4; i++) { + int flags; + DATA_BLOB chal, nt_resp, lm_resp, names_blob; + struct netlogon_creds_CredentialState *creds_state; + struct netr_Authenticator netr_auth, netr_auth2; + struct netr_NetworkInfo ninfo; + struct netr_PasswordInfo pinfo; + struct netr_LogonSamLogon r; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative; + struct netr_Authenticator return_authenticator; + + flags = CLI_CRED_LANMAN_AUTH | CLI_CRED_NTLM_AUTH | + CLI_CRED_NTLMv2_AUTH; + + chal = data_blob_talloc(mem_ctx, NULL, 8); + if (chal.data == NULL) { + torture_comment(tctx, "data_blob_talloc failed\n"); + goto done; + } + + generate_random_buffer(chal.data, chal.length); + names_blob = NTLMv2_generate_names_blob( + mem_ctx, + cli_credentials_get_workstation(wks_creds), + cli_credentials_get_domain(wks_creds)); + status = cli_credentials_get_ntlm_response( + user_creds, mem_ctx, &flags, chal, NULL, names_blob, + &lm_resp, &nt_resp, NULL, NULL); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "cli_credentials_get_ntlm_response failed:" + " %s\n", nt_errstr(status)); + goto done; + } + + creds_state = cli_credentials_get_netlogon_creds(wks_creds); + netlogon_creds_client_authenticator(creds_state, &netr_auth); + + ninfo.identity_info.account_name.string = + cli_credentials_get_username(user_creds); + ninfo.identity_info.domain_name.string = + cli_credentials_get_domain(user_creds); + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.workstation.string = + cli_credentials_get_workstation(user_creds); + memcpy(ninfo.challenge, chal.data, sizeof(ninfo.challenge)); + ninfo.nt.length = nt_resp.length; + ninfo.nt.data = nt_resp.data; + ninfo.lm.length = lm_resp.length; + ninfo.lm.data = lm_resp.data; + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + ZERO_STRUCT(netr_auth2); + r.in.computer_name = + cli_credentials_get_workstation(wks_creds); + r.in.credential = &netr_auth; + r.in.return_authenticator = &netr_auth2; + r.in.logon_level = NetlogonNetworkInformation; + r.in.validation_level = i; + r.in.logon = &logon; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + r.out.return_authenticator = &return_authenticator; + + status = dcerpc_netr_LogonSamLogon_r(net_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "netr_LogonSamLogon failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "netr_LogonSamLogon failed: %s\n", + nt_errstr(r.out.result)); + goto done; + } + + if ((r.out.return_authenticator == NULL) || + (!netlogon_creds_client_check(creds_state, + &r.out.return_authenticator->cred))) { + torture_comment(tctx, "Credentials check failed!\n"); + goto done; + } + + netlogon_creds_client_authenticator(creds_state, &netr_auth); + + pinfo.identity_info = ninfo.identity_info; + ZERO_STRUCT(pinfo.lmpassword.hash); + E_md4hash(cli_credentials_get_password(user_creds), + pinfo.ntpassword.hash); + + logon.password = &pinfo; + + /* + * We don't use this here: + * + * netlogon_creds_encrypt_samlogon_logon(creds_state, + * NetlogonInteractiveInformation, + * &logon); + * + * in order to detect bugs + */ + netlogon_creds_aes_encrypt(creds_state, pinfo.ntpassword.hash, 16); + + r.in.logon_level = NetlogonInteractiveInformation; + r.in.logon = &logon; + r.out.return_authenticator = &return_authenticator; + + status = dcerpc_netr_LogonSamLogon_r(net_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "netr_LogonSamLogon failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "netr_LogonSamLogon failed: %s\n", + nt_errstr(r.out.result)); + goto done; + } + + if ((r.out.return_authenticator == NULL) || + (!netlogon_creds_client_check(creds_state, + &r.out.return_authenticator->cred))) { + torture_comment(tctx, "Credentials check failed!\n"); + goto done; + } + } + + { + struct netr_ServerPasswordSet s; + char *password = generate_random_password(wks_creds, 8, 255); + struct netlogon_creds_CredentialState *creds_state; + struct netr_Authenticator credential, return_authenticator; + struct samr_Password new_password; + + s.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + s.in.computer_name = cli_credentials_get_workstation(wks_creds); + s.in.account_name = talloc_asprintf( + mem_ctx, "%s$", s.in.computer_name); + s.in.secure_channel_type = SEC_CHAN_WKSTA; + s.in.credential = &credential; + s.in.new_password = &new_password; + s.out.return_authenticator = &return_authenticator; + + E_md4hash(password, new_password.hash); + + creds_state = cli_credentials_get_netlogon_creds(wks_creds); + netlogon_creds_des_encrypt(creds_state, &new_password); + netlogon_creds_client_authenticator(creds_state, &credential); + + status = dcerpc_netr_ServerPasswordSet_r(net_handle, mem_ctx, &s); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "ServerPasswordSet - %s\n", nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_comment(tctx, "ServerPasswordSet - %s\n", nt_errstr(s.out.result)); + goto done; + } + + if (!netlogon_creds_client_check(creds_state, + &s.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + cli_credentials_set_password(wks_creds, password, + CRED_SPECIFIED); + } + + ret = true; + done: + talloc_free(mem_ctx); + return ret; +} + +/* + * Delete the wks account again + */ + +static bool leave(struct torture_context *tctx, + struct smbcli_state *cli, + struct cli_credentials *admin_creds, + struct cli_credentials *wks_creds) +{ + char *wks_name = talloc_asprintf( + NULL, "%s$", cli_credentials_get_workstation(wks_creds)); + bool ret; + + ret = delete_user(tctx, cli, admin_creds, wks_name); + talloc_free(wks_name); + return ret; +} + +/* + * Test the Samba3 DC code a bit. Join, do some schan netlogon ops, leave + */ + +static bool torture_netlogon_samba3(struct torture_context *torture) +{ + NTSTATUS status; + struct smbcli_state *cli; + struct cli_credentials *wks_creds; + const char *wks_name; + int i; + struct smbcli_options options; + struct smbcli_session_options session_options; + + wks_name = torture_setting_string(torture, "wksname", NULL); + torture_assert(torture, wks_name != NULL, "wksname not set"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(torture, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok(torture, status, "smbcli_full_connection failed\n"); + + wks_creds = cli_credentials_init(torture); + if (wks_creds == NULL) { + torture_fail(torture, "cli_credentials_init failed\n"); + } + + cli_credentials_set_conf(wks_creds, torture->lp_ctx); + cli_credentials_set_secure_channel_type(wks_creds, SEC_CHAN_WKSTA); + cli_credentials_set_username(wks_creds, wks_name, CRED_SPECIFIED); + cli_credentials_set_workstation(wks_creds, wks_name, CRED_SPECIFIED); + cli_credentials_set_password(wks_creds, + generate_random_password(wks_creds, 8, 255), + CRED_SPECIFIED); + + torture_assert(torture, + join3(torture, cli, false, NULL, wks_creds), + "join failed"); + + cli_credentials_set_domain( + samba_cmdline_get_creds(), + cli_credentials_get_domain(wks_creds), + CRED_SPECIFIED); + + for (i=0; i<2; i++) { + + /* Do this more than once, the routine "schan" changes + * the workstation password using the netlogon + * password change routine */ + + int j; + + torture_assert(torture, + auth2(torture, cli, wks_creds), + "auth2 failed"); + + for (j=0; j<2; j++) { + torture_assert(torture, + schan(torture, cli, wks_creds, + samba_cmdline_get_creds()), + "schan failed"); + } + } + + torture_assert(torture, + leave(torture, cli, NULL, wks_creds), + "leave failed"); + + return true; +} + +/* + * Do a simple join, testjoin and leave using specified smb and samr + * credentials + */ + +static bool test_join3(struct torture_context *tctx, + bool use_level25, + struct cli_credentials *smb_creds, + struct cli_credentials *samr_creds, + const char *wks_name) +{ + NTSTATUS status; + struct smbcli_state *cli; + struct cli_credentials *wks_creds; + struct smbcli_options options; + struct smbcli_session_options session_options; + + lpcfg_smbcli_options(tctx->lp_ctx, &options); + lpcfg_smbcli_session_options(tctx->lp_ctx, &session_options); + + status = smbcli_full_connection(tctx, &cli, + torture_setting_string(tctx, "host", NULL), + lpcfg_smb_ports(tctx->lp_ctx), + "IPC$", NULL, lpcfg_socket_options(tctx->lp_ctx), + smb_creds, lpcfg_resolve_context(tctx->lp_ctx), + tctx->ev, &options, &session_options, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + torture_assert_ntstatus_ok(tctx, status, + "smbcli_full_connection failed"); + + wks_creds = cli_credentials_init(cli); + torture_assert(tctx, wks_creds, "cli_credentials_init failed"); + + cli_credentials_set_conf(wks_creds, tctx->lp_ctx); + cli_credentials_set_secure_channel_type(wks_creds, SEC_CHAN_WKSTA); + cli_credentials_set_username(wks_creds, wks_name, CRED_SPECIFIED); + cli_credentials_set_workstation(wks_creds, wks_name, CRED_SPECIFIED); + cli_credentials_set_password(wks_creds, + generate_random_password(wks_creds, 8, 255), + CRED_SPECIFIED); + + torture_assert(tctx, + join3(tctx, cli, use_level25, samr_creds, wks_creds), + "join failed"); + + cli_credentials_set_domain( + samba_cmdline_get_creds(), + cli_credentials_get_domain(wks_creds), + CRED_SPECIFIED); + + torture_assert(tctx, + auth2(tctx, cli, wks_creds), + "auth2 failed"); + + torture_assert(tctx, + leave(tctx, cli, samr_creds, wks_creds), + "leave failed"); + + talloc_free(cli); + + return true; +} + +/* + * Test the different session key variants. Do it by joining, this uses the + * session key in the setpassword routine. Test the join by doing the auth2. + */ + +static bool torture_samba3_sessionkey(struct torture_context *torture) +{ + struct cli_credentials *anon_creds; + const char *wks_name; + + + wks_name = torture_setting_string(torture, "wksname", NULL); + torture_assert(torture, wks_name != NULL, "wksname not set"); + + if (!(anon_creds = cli_credentials_init_anon(torture))) { + torture_fail(torture, "create_anon_creds failed\n"); + } + + cli_credentials_set_workstation(anon_creds, wks_name, CRED_SPECIFIED); + + + if (!torture_setting_bool(torture, "samba3", false)) { + + /* Samba3 in the build farm right now does this happily. Need + * to fix :-) */ + + if (test_join3(torture, false, anon_creds, NULL, wks_name)) { + torture_fail(torture, "join using anonymous bind on an anonymous smb " + "connection succeeded -- HUH??\n"); + } + } + + torture_assert(torture, + test_join3(torture, false, samba_cmdline_get_creds(), + NULL, wks_name), + "join using anonymous bind on an authenticated smb connection failed"); + + /* + * The following two are tests for setuserinfolevel 25 + */ + + torture_assert(torture, + test_join3(torture, true, samba_cmdline_get_creds(), + NULL, wks_name), + "join using anonymous bind on an authenticated smb connection failed"); + + return true; +} + +/* + * Sane wrapper around lsa_LookupNames + */ + +static struct dom_sid *name2sid(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct dcerpc_pipe *p, + const char *name, + const char *domain) +{ + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + struct lsa_Close c; + NTSTATUS status; + struct policy_handle handle; + struct lsa_LookupNames l; + struct lsa_TransSidArray sids; + struct lsa_RefDomainList *domains = NULL; + struct lsa_String lsa_name; + uint32_t count = 0; + struct dom_sid *result; + TALLOC_CTX *tmp_ctx; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + return NULL; + } + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + status = dcerpc_lsa_OpenPolicy2_r(b, tmp_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "OpenPolicy2 failed - %s\n", nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "OpenPolicy2 failed - %s\n", nt_errstr(r.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + sids.count = 0; + sids.sids = NULL; + + lsa_name.string = talloc_asprintf(tmp_ctx, "%s\\%s", domain, name); + + l.in.handle = &handle; + l.in.num_names = 1; + l.in.names = &lsa_name; + l.in.sids = &sids; + l.in.level = 1; + l.in.count = &count; + l.out.count = &count; + l.out.sids = &sids; + l.out.domains = &domains; + + status = dcerpc_lsa_LookupNames_r(b, tmp_ctx, &l); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "LookupNames of %s failed - %s\n", lsa_name.string, + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + if (!NT_STATUS_IS_OK(l.out.result)) { + torture_comment(tctx, "LookupNames of %s failed - %s\n", lsa_name.string, + nt_errstr(l.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + result = dom_sid_add_rid(mem_ctx, domains->domains[0].sid, + l.out.sids->sids[0].rid); + + c.in.handle = &handle; + c.out.handle = &handle; + + status = dcerpc_lsa_Close_r(b, tmp_ctx, &c); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_lsa_Close failed - %s\n", nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + if (!NT_STATUS_IS_OK(c.out.result)) { + torture_comment(tctx, "dcerpc_lsa_Close failed - %s\n", nt_errstr(c.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + talloc_free(tmp_ctx); + return result; +} + +/* + * Find out the user SID on this connection + */ + +static struct dom_sid *whoami(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_tree *tree) +{ + struct dcerpc_pipe *lsa; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_GetUserName r; + NTSTATUS status; + struct lsa_String *authority_name_p = NULL; + struct lsa_String *account_name_p = NULL; + struct dom_sid *result; + + status = pipe_bind_smb(tctx, mem_ctx, tree, "\\pipe\\lsarpc", + &ndr_table_lsarpc, &lsa); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "Could not bind to LSA: %s\n", + nt_errstr(status)); + return NULL; + } + lsa_handle = lsa->binding_handle; + + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "GetUserName failed - %s\n", + nt_errstr(status)); + talloc_free(lsa); + return NULL; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_warning(tctx, "GetUserName failed - %s\n", + nt_errstr(r.out.result)); + talloc_free(lsa); + return NULL; + } + + result = name2sid(tctx, mem_ctx, lsa, account_name_p->string, + authority_name_p->string); + + talloc_free(lsa); + return result; +} + +static int destroy_tree(struct smbcli_tree *tree) +{ + smb_tree_disconnect(tree); + return 0; +} + +/* + * Do a tcon, given a session + */ + +static NTSTATUS secondary_tcon(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_session *session, + const char *sharename, + struct smbcli_tree **res) +{ + struct smbcli_tree *result; + TALLOC_CTX *tmp_ctx; + union smb_tcon tcon; + NTSTATUS status; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + return NT_STATUS_NO_MEMORY; + } + + if (!(result = smbcli_tree_init(session, mem_ctx, false))) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + tcon.generic.level = RAW_TCON_TCONX; + tcon.tconx.in.flags = TCONX_FLAG_EXTENDED_RESPONSE; + tcon.tconx.in.flags |= TCONX_FLAG_EXTENDED_SIGNATURES; + tcon.tconx.in.password = data_blob(NULL, 0); + tcon.tconx.in.path = sharename; + tcon.tconx.in.device = "?????"; + + status = smb_raw_tcon(result, tmp_ctx, &tcon); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "smb_raw_tcon failed: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return status; + } + + result->tid = tcon.tconx.out.tid; + + if (tcon.tconx.out.options & SMB_EXTENDED_SIGNATURES) { + smb1cli_session_protect_session_key(result->session->smbXcli); + } + + result = talloc_steal(mem_ctx, result); + talloc_set_destructor(result, destroy_tree); + talloc_free(tmp_ctx); + *res = result; + return NT_STATUS_OK; +} + +/* + * Test the getusername behaviour + */ + +static bool torture_samba3_rpc_getusername(struct torture_context *torture) +{ + NTSTATUS status; + struct smbcli_state *cli; + bool ret = true; + struct dom_sid *user_sid; + struct dom_sid *created_sid; + struct cli_credentials *anon_creds; + struct cli_credentials *user_creds; + char *domain_name; + struct smbcli_options options; + struct smbcli_session_options session_options; + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + if (!(anon_creds = cli_credentials_init_anon(torture))) { + torture_fail(torture, "create_anon_creds failed\n"); + } + + status = smbcli_full_connection( + torture, &cli, torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), anon_creds, + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok(torture, status, "anon smbcli_full_connection failed\n"); + + if (!(user_sid = whoami(torture, torture, cli->tree))) { + torture_fail(torture, "whoami on anon connection failed\n"); + } + + torture_assert_sid_equal(torture, user_sid, dom_sid_parse_talloc(torture, "s-1-5-7"), + "Anon lsa_GetUserName returned unexpected SID"); + + talloc_free(cli); + + status = smbcli_full_connection( + torture, &cli, torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), torture->ev, &options, + &session_options, lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok(torture, status, "smbcli_full_connection failed\n"); + + if (!(user_sid = whoami(torture, torture, cli->tree))) { + torture_fail(torture, "whoami on auth'ed connection failed\n"); + } + + if (!(user_creds = cli_credentials_init(torture))) { + torture_fail(torture, "cli_credentials_init failed\n"); + } + + cli_credentials_set_conf(user_creds, torture->lp_ctx); + cli_credentials_set_username(user_creds, "torture_username", + CRED_SPECIFIED); + cli_credentials_set_password(user_creds, + generate_random_password(user_creds, 8, 255), + CRED_SPECIFIED); + + if (!create_user(torture, torture, cli, NULL, + cli_credentials_get_username(user_creds), + cli_credentials_get_password(user_creds), + &domain_name, &created_sid)) { + torture_fail(torture, "create_user failed\n"); + } + + cli_credentials_set_domain(user_creds, domain_name, + CRED_SPECIFIED); + + { + struct smbcli_session *session2; + struct smb_composite_sesssetup setup; + struct smbcli_tree *tree; + + session2 = smbcli_session_init(cli->transport, torture, false, session_options); + if (session2 == NULL) { + torture_fail(torture, "smbcli_session_init failed\n"); + } + + setup.in.sesskey = cli->transport->negotiate.sesskey; + setup.in.capabilities = cli->transport->negotiate.capabilities; + setup.in.workgroup = ""; + setup.in.credentials = user_creds; + setup.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(session2, &setup); + torture_assert_ntstatus_ok(torture, status, "session setup with new user failed"); + + session2->vuid = setup.out.vuid; + + if (!NT_STATUS_IS_OK(secondary_tcon(torture, torture, session2, + "IPC$", &tree))) { + torture_fail(torture, "secondary_tcon failed\n"); + } + + if (!(user_sid = whoami(torture, torture, tree))) { + torture_fail_goto(torture, del, "whoami on user connection failed\n"); + ret = false; + goto del; + } + + talloc_free(tree); + } + + torture_comment(torture, "Created %s, found %s\n", + dom_sid_string(torture, created_sid), + dom_sid_string(torture, user_sid)); + + if (!dom_sid_equal(created_sid, user_sid)) { + ret = false; + } + + del: + if (!delete_user(torture, cli, + NULL, + cli_credentials_get_username(user_creds))) { + torture_fail(torture, "delete_user failed\n"); + } + + return ret; +} + +static bool test_NetShareGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *sharename) +{ + NTSTATUS status; + struct srvsvc_NetShareGetInfo r; + union srvsvc_NetShareInfo info; + uint32_t levels[] = { 0, 1, 2, 501, 502, 1004, 1005, 1006, 1007, 1501 }; + int i; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.share_name = sharename; + r.out.info = &info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + r.in.level = levels[i]; + + torture_comment(tctx, "Testing NetShareGetInfo level %u on share '%s'\n", + r.in.level, r.in.share_name); + + status = dcerpc_srvsvc_NetShareGetInfo_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "NetShareGetInfo level %u on share '%s' failed" + " - %s\n", r.in.level, r.in.share_name, + nt_errstr(status)); + ret = false; + continue; + } + if (!W_ERROR_IS_OK(r.out.result)) { + torture_warning(tctx, "NetShareGetInfo level %u on share '%s' failed " + "- %s\n", r.in.level, r.in.share_name, + win_errstr(r.out.result)); + ret = false; + continue; + } + } + + return ret; +} + +static bool test_NetShareEnum(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char **one_sharename) +{ + NTSTATUS status; + struct srvsvc_NetShareEnum r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr0 c0; + struct srvsvc_NetShareCtr1 c1; + struct srvsvc_NetShareCtr2 c2; + struct srvsvc_NetShareCtr501 c501; + struct srvsvc_NetShareCtr502 c502; + struct srvsvc_NetShareCtr1004 c1004; + struct srvsvc_NetShareCtr1005 c1005; + struct srvsvc_NetShareCtr1006 c1006; + struct srvsvc_NetShareCtr1007 c1007; + uint32_t totalentries = 0; + uint32_t levels[] = { 0, 1, 2, 501, 502, 1004, 1005, 1006, 1007 }; + int i; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 501: + ZERO_STRUCT(c501); + info_ctr.ctr.ctr501 = &c501; + break; + case 502: + ZERO_STRUCT(c502); + info_ctr.ctr.ctr502 = &c502; + break; + case 1004: + ZERO_STRUCT(c1004); + info_ctr.ctr.ctr1004 = &c1004; + break; + case 1005: + ZERO_STRUCT(c1005); + info_ctr.ctr.ctr1005 = &c1005; + break; + case 1006: + ZERO_STRUCT(c1006); + info_ctr.ctr.ctr1006 = &c1006; + break; + case 1007: + ZERO_STRUCT(c1007); + info_ctr.ctr.ctr1007 = &c1007; + break; + } + + torture_comment(tctx, "Testing NetShareEnum level %u\n", info_ctr.level); + + status = dcerpc_srvsvc_NetShareEnum_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "NetShareEnum level %u failed - %s\n", + info_ctr.level, nt_errstr(status)); + ret = false; + continue; + } + if (!W_ERROR_IS_OK(r.out.result)) { + torture_warning(tctx, "NetShareEnum level %u failed - %s\n", + info_ctr.level, win_errstr(r.out.result)); + continue; + } + if (info_ctr.level == 0) { + struct srvsvc_NetShareCtr0 *ctr = r.out.info_ctr->ctr.ctr0; + if (ctr->count > 0) { + *one_sharename = ctr->array[0].name; + } + } + } + + return ret; +} + +static bool torture_samba3_rpc_srvsvc(struct torture_context *torture) +{ + struct dcerpc_pipe *p; + const char *sharename = NULL; + bool ret = true; + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_srvsvc), + "failed to open srvsvc"); + + ret &= test_NetShareEnum(torture, p, &sharename); + if (sharename == NULL) { + torture_comment(torture, "did not get sharename\n"); + } else { + ret &= test_NetShareGetInfo(torture, p, sharename); + } + + return ret; +} + +/* + * Do a ReqChallenge/Auth2 with a random wks name, make sure it returns + * NT_STATUS_NO_SAM_ACCOUNT + */ + +static bool torture_samba3_rpc_randomauth2(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + struct dcerpc_pipe *net_pipe; + struct dcerpc_binding_handle *net_handle; + char *wksname; + bool result = false; + NTSTATUS status; + struct netr_ServerReqChallenge r; + struct netr_Credential netr_cli_creds; + struct netr_Credential netr_srv_creds; + uint32_t negotiate_flags; + struct netr_ServerAuthenticate2 a; + struct netlogon_creds_CredentialState *creds_state; + struct netr_Credential netr_cred; + struct samr_Password mach_pw; + struct smbcli_state *cli; + + if (!(mem_ctx = talloc_new(torture))) { + torture_comment(torture, "talloc_new failed\n"); + return false; + } + + if (!(wksname = generate_random_str_list( + mem_ctx, 14, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"))) { + torture_comment(torture, "generate_random_str_list failed\n"); + goto done; + } + + if (!(torture_open_connection_share( + mem_ctx, &cli, + torture, torture_setting_string(torture, "host", NULL), + "IPC$", torture->ev))) { + torture_comment(torture, "IPC$ connection failed\n"); + goto done; + } + + status = pipe_bind_smb(torture, mem_ctx, cli->tree, "\\netlogon", + &ndr_table_netlogon, &net_pipe); + torture_assert_ntstatus_ok_goto(torture, status, result, done, + "pipe_bind_smb failed"); + net_handle = net_pipe->binding_handle; + + r.in.computer_name = wksname; + r.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + if (r.in.server_name == NULL) { + torture_comment(torture, "talloc_asprintf failed\n"); + goto done; + } + generate_random_buffer(netr_cli_creds.data, + sizeof(netr_cli_creds.data)); + r.in.credentials = &netr_cli_creds; + r.out.return_credentials = &netr_srv_creds; + + status = dcerpc_netr_ServerReqChallenge_r(net_handle, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "netr_ServerReqChallenge failed: %s\n", + nt_errstr(status)); + goto done; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(torture, "netr_ServerReqChallenge failed: %s\n", + nt_errstr(r.out.result)); + goto done; + } + + negotiate_flags = NETLOGON_NEG_AUTH2_FLAGS; + E_md4hash("foobar", mach_pw.hash); + + a.in.server_name = talloc_asprintf( + mem_ctx, "\\\\%s", dcerpc_server_name(net_pipe)); + a.in.account_name = talloc_asprintf( + mem_ctx, "%s$", wksname); + a.in.computer_name = wksname; + a.in.secure_channel_type = SEC_CHAN_WKSTA; + a.in.negotiate_flags = &negotiate_flags; + a.out.negotiate_flags = &negotiate_flags; + a.in.credentials = &netr_cred; + a.out.return_credentials = &netr_cred; + + creds_state = netlogon_creds_client_init(mem_ctx, + a.in.account_name, + a.in.computer_name, + a.in.secure_channel_type, + r.in.credentials, + r.out.return_credentials, &mach_pw, + &netr_cred, negotiate_flags); + torture_assert(torture, (creds_state != NULL), "memory allocation failed"); + + status = dcerpc_netr_ServerAuthenticate2_r(net_handle, mem_ctx, &a); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (!NT_STATUS_EQUAL(a.out.result, NT_STATUS_NO_TRUST_SAM_ACCOUNT)) { + torture_comment(torture, "dcerpc_netr_ServerAuthenticate2 returned %s, " + "expected NT_STATUS_NO_TRUST_SAM_ACCOUNT\n", + nt_errstr(a.out.result)); + goto done; + } + + result = true; + done: + talloc_free(mem_ctx); + return result; +} + +static struct security_descriptor *get_sharesec(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_session *sess, + const char *sharename) +{ + struct smbcli_tree *tree; + TALLOC_CTX *tmp_ctx; + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + NTSTATUS status; + struct srvsvc_NetShareGetInfo r; + union srvsvc_NetShareInfo info; + struct security_descriptor *result; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + torture_comment(tctx, "talloc_new failed\n"); + return NULL; + } + + if (!NT_STATUS_IS_OK(secondary_tcon(tctx, tmp_ctx, sess, "IPC$", &tree))) { + torture_comment(tctx, "secondary_tcon failed\n"); + talloc_free(tmp_ctx); + return NULL; + } + + status = pipe_bind_smb(tctx, mem_ctx, tree, "\\pipe\\srvsvc", + &ndr_table_srvsvc, &p); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "could not bind to srvsvc pipe: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + b = p->binding_handle; + +#if 0 + p->conn->flags |= DCERPC_DEBUG_PRINT_IN | DCERPC_DEBUG_PRINT_OUT; +#endif + + r.in.server_unc = talloc_asprintf(tmp_ctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.share_name = sharename; + r.in.level = 502; + r.out.info = &info; + + status = dcerpc_srvsvc_NetShareGetInfo_r(b, tmp_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "srvsvc_NetShareGetInfo failed: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return NULL; + } + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "srvsvc_NetShareGetInfo failed: %s\n", + win_errstr(r.out.result)); + talloc_free(tmp_ctx); + return NULL; + } + + result = talloc_steal(mem_ctx, info.info502->sd_buf.sd); + talloc_free(tmp_ctx); + return result; +} + +static NTSTATUS set_sharesec(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct smbcli_session *sess, + const char *sharename, + struct security_descriptor *sd) +{ + struct smbcli_tree *tree; + TALLOC_CTX *tmp_ctx; + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + NTSTATUS status; + struct sec_desc_buf i; + struct srvsvc_NetShareSetInfo r; + union srvsvc_NetShareInfo info; + uint32_t error = 0; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + torture_comment(tctx, "talloc_new failed\n"); + return NT_STATUS_NO_MEMORY; + } + + if (!NT_STATUS_IS_OK(secondary_tcon(tctx, tmp_ctx, sess, "IPC$", &tree))) { + torture_comment(tctx, "secondary_tcon failed\n"); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + status = pipe_bind_smb(tctx, mem_ctx, tree, "\\pipe\\srvsvc", + &ndr_table_srvsvc, &p); + if (!NT_STATUS_IS_OK(status)) { + torture_warning(tctx, "could not bind to srvsvc pipe: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + b = p->binding_handle; + +#if 0 + p->conn->flags |= DCERPC_DEBUG_PRINT_IN | DCERPC_DEBUG_PRINT_OUT; +#endif + + r.in.server_unc = talloc_asprintf(tmp_ctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.share_name = sharename; + r.in.level = 1501; + i.sd = sd; + info.info1501 = &i; + r.in.info = &info; + r.in.parm_error = &error; + + status = dcerpc_srvsvc_NetShareSetInfo_r(b, tmp_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "srvsvc_NetShareSetInfo failed: %s\n", + nt_errstr(status)); + } + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "srvsvc_NetShareSetInfo failed: %s\n", + win_errstr(r.out.result)); + status = werror_to_ntstatus(r.out.result); + } + talloc_free(tmp_ctx); + return status; +} + +bool try_tcon(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct security_descriptor *orig_sd, + struct smbcli_session *session, + const char *sharename, const struct dom_sid *user_sid, + unsigned int access_mask, NTSTATUS expected_tcon, + NTSTATUS expected_mkdir) +{ + TALLOC_CTX *tmp_ctx; + struct smbcli_tree *rmdir_tree, *tree; + struct dom_sid *domain_sid; + uint32_t rid; + struct security_descriptor *sd; + NTSTATUS status; + bool ret = true; + + if (!(tmp_ctx = talloc_new(mem_ctx))) { + torture_comment(tctx, "talloc_new failed\n"); + return false; + } + + status = secondary_tcon(tctx, tmp_ctx, session, sharename, &rmdir_tree); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "first tcon to delete dir failed\n"); + talloc_free(tmp_ctx); + return false; + } + + smbcli_rmdir(rmdir_tree, "sharesec_testdir"); + + if (!NT_STATUS_IS_OK(dom_sid_split_rid(tmp_ctx, user_sid, + &domain_sid, &rid))) { + torture_comment(tctx, "dom_sid_split_rid failed\n"); + talloc_free(tmp_ctx); + return false; + } + + sd = security_descriptor_dacl_create( + tmp_ctx, 0, "S-1-5-32-544", + dom_sid_string(mem_ctx, dom_sid_add_rid(mem_ctx, domain_sid, + DOMAIN_RID_USERS)), + dom_sid_string(mem_ctx, user_sid), + SEC_ACE_TYPE_ACCESS_ALLOWED, access_mask, 0, NULL); + if (sd == NULL) { + torture_comment(tctx, "security_descriptor_dacl_create failed\n"); + talloc_free(tmp_ctx); + return false; + } + + status = set_sharesec(tctx, mem_ctx, session, sharename, sd); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "custom set_sharesec failed: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return false; + } + + status = secondary_tcon(tctx, tmp_ctx, session, sharename, &tree); + if (!NT_STATUS_EQUAL(status, expected_tcon)) { + torture_comment(tctx, "Expected %s, got %s\n", nt_errstr(expected_tcon), + nt_errstr(status)); + ret = false; + goto done; + } + + if (!NT_STATUS_IS_OK(status)) { + /* An expected non-access, no point in trying to write */ + goto done; + } + + status = smbcli_mkdir(tree, "sharesec_testdir"); + if (!NT_STATUS_EQUAL(status, expected_mkdir)) { + torture_warning(tctx, "Expected %s, got %s\n", + nt_errstr(expected_mkdir), nt_errstr(status)); + ret = false; + } + + done: + smbcli_rmdir(rmdir_tree, "sharesec_testdir"); + + status = set_sharesec(tctx, mem_ctx, session, sharename, orig_sd); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "custom set_sharesec failed: %s\n", + nt_errstr(status)); + talloc_free(tmp_ctx); + return false; + } + + talloc_free(tmp_ctx); + return ret; +} + +static bool torture_samba3_rpc_sharesec(struct torture_context *torture) +{ + struct smbcli_state *cli = NULL; + struct security_descriptor *sd = NULL; + struct dom_sid *user_sid = NULL; + const char *testuser_passwd = NULL; + struct cli_credentials *test_credentials = NULL; + struct smbcli_options options; + struct smbcli_session_options session_options; + NTSTATUS status; + struct test_join *tj = NULL; + struct dcerpc_pipe *lsa_pipe = NULL; + const char *priv_array[1]; + + /* Create a new user. The normal user has SeBackup and SeRestore + privs so we can't lock them out with a share security descriptor. */ + tj = torture_create_testuser(torture, + "sharesec_user", + torture_setting_string(torture, "workgroup", NULL), + ACB_NORMAL, + &testuser_passwd); + if (!tj) { + torture_fail(torture, "Creating sharesec_user failed\n"); + } + + /* Give them SeDiskOperatorPrivilege but no other privs. */ + status = torture_rpc_connection(torture, &lsa_pipe, &ndr_table_lsarpc); + if (!NT_STATUS_IS_OK(status)) { + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "Error connecting to LSA pipe"); + } + + priv_array[0] = "SeDiskOperatorPrivilege"; + if (!torture_setup_privs(torture, + lsa_pipe, + 1, + priv_array, + torture_join_user_sid(tj))) { + talloc_free(lsa_pipe); + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "Failed to setup privs\n"); + } + talloc_free(lsa_pipe); + + test_credentials = cli_credentials_init(torture); + cli_credentials_set_workstation(test_credentials, "localhost", CRED_SPECIFIED); + cli_credentials_set_domain(test_credentials, lpcfg_workgroup(torture->lp_ctx), + CRED_SPECIFIED); + cli_credentials_set_username(test_credentials, "sharesec_user", CRED_SPECIFIED); + cli_credentials_set_password(test_credentials, testuser_passwd, CRED_SPECIFIED); + + ZERO_STRUCT(options); + ZERO_STRUCT(session_options); + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(torture, + &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + NULL, + lpcfg_socket_options(torture->lp_ctx), + test_credentials, + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, + &options, + &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(cli); + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "Failed to open connection\n"); + } + + if (!(user_sid = whoami(torture, torture, cli->tree))) { + talloc_free(cli); + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "whoami failed\n"); + } + + sd = get_sharesec(torture, torture, cli->session, + torture_setting_string(torture, "share", NULL)); + + if (!try_tcon(torture, torture, sd, cli->session, + torture_setting_string(torture, "share", NULL), + user_sid, 0, NT_STATUS_ACCESS_DENIED, NT_STATUS_OK)) { + talloc_free(cli); + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "failed to test tcon with 0 access_mask"); + } + + if (!try_tcon(torture, torture, sd, cli->session, + torture_setting_string(torture, "share", NULL), + user_sid, SEC_FILE_READ_DATA, NT_STATUS_OK, + NT_STATUS_MEDIA_WRITE_PROTECTED)) { + talloc_free(cli); + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + torture_fail(torture, "failed to test tcon with SEC_FILE_READ_DATA access_mask"); + } + + /* sharesec_user doesn't have any rights on the underlying file system. + Go back to the normal user. */ + + talloc_free(cli); + cli = NULL; + torture_delete_testuser(torture, tj, "sharesec_user"); + talloc_free(tj); + tj = NULL; + + if (!(torture_open_connection_share( + torture, &cli, torture, torture_setting_string(torture, "host", NULL), + "IPC$", torture->ev))) { + torture_fail(torture, "IPC$ connection failed\n"); + } + + if (!(user_sid = whoami(torture, torture, cli->tree))) { + torture_fail(torture, "whoami failed\n"); + } + torture_assert(torture, try_tcon( + torture, torture, sd, cli->session, + torture_setting_string(torture, "share", NULL), + user_sid, SEC_FILE_ALL, NT_STATUS_OK, NT_STATUS_OK), + "failed to test tcon with SEC_FILE_ALL access_mask"); + + return true; +} + +static bool torture_samba3_rpc_lsa(struct torture_context *torture) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + struct policy_handle lsa_handle; + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_lsarpc), + "failed to setup lsarpc"); + + b = p->binding_handle; + + { + struct lsa_ObjectAttribute attr; + struct lsa_OpenPolicy2 o; + o.in.system_name = talloc_asprintf( + torture, "\\\\%s", dcerpc_server_name(p)); + ZERO_STRUCT(attr); + o.in.attr = &attr; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &lsa_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_lsa_OpenPolicy2_r(b, torture, &o), + "dcerpc_lsa_OpenPolicy2 failed"); + torture_assert_ntstatus_ok(torture, o.out.result, + "dcerpc_lsa_OpenPolicy2 failed"); + } + + { + int i; + int levels[] = { 2,3,5,6 }; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + struct lsa_QueryInfoPolicy r; + union lsa_PolicyInformation *info = NULL; + r.in.handle = &lsa_handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(torture, + dcerpc_lsa_QueryInfoPolicy_r(b, torture, &r), + talloc_asprintf(torture, "dcerpc_lsa_QueryInfoPolicy level %d failed", levels[i])); + torture_assert_ntstatus_ok(torture, r.out.result, + talloc_asprintf(torture, "dcerpc_lsa_QueryInfoPolicy level %d failed", levels[i])); + } + } + + return true; +} + +static NTSTATUS get_servername(TALLOC_CTX *mem_ctx, struct smbcli_tree *tree, + char **name) +{ + struct rap_WserverGetInfo r; + NTSTATUS status; + char servername[17]; + size_t converted_size; + + r.in.level = 0; + r.in.bufsize = 0xffff; + + status = smbcli_rap_netservergetinfo(tree, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + memcpy(servername, r.out.info.info0.name, 16); + servername[16] = '\0'; + + if (!pull_ascii_talloc(mem_ctx, name, servername, &converted_size)) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static bool rap_get_servername(struct torture_context *tctx, + char **servername) +{ + struct smbcli_state *cli; + + torture_assert(tctx, + torture_open_connection_share(tctx, &cli, tctx, torture_setting_string(tctx, "host", NULL), + "IPC$", tctx->ev), + "IPC$ connection failed"); + + torture_assert_ntstatus_ok(tctx, + get_servername(tctx, cli->tree, servername), + "get_servername failed"); + + talloc_free(cli); + + return true; +} + +static bool find_printers(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char ***printers, + size_t *num_printers) +{ + struct srvsvc_NetShareEnum r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr1 c1_in; + struct srvsvc_NetShareCtr1 *c1; + uint32_t totalentries = 0; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(c1_in); + info_ctr.level = 1; + info_ctr.ctr.ctr1 = &c1_in; + + r.in.server_unc = talloc_asprintf( + tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + torture_assert_ntstatus_ok(tctx, + dcerpc_srvsvc_NetShareEnum_r(b, tctx, &r), + "NetShareEnum level 1 failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetShareEnum level 1 failed"); + + *printers = NULL; + *num_printers = 0; + c1 = r.out.info_ctr->ctr.ctr1; + for (i=0; i<c1->count; i++) { + if (c1->array[i].type != STYPE_PRINTQ) { + continue; + } + if (!add_string_to_array(tctx, c1->array[i].name, + printers, num_printers)) { + return false; + } + } + + return true; +} + +static bool enumprinters(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, int level, int *num_printers) +{ + struct spoolss_EnumPrinters r; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_PrinterInfo *info; + + r.in.flags = PRINTER_ENUM_LOCAL; + r.in.server = talloc_asprintf(tctx, "\\\\%s", servername); + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "dcerpc_spoolss_EnumPrinters failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "EnumPrinters unexpected return code should be WERR_INSUFFICIENT_BUFFER"); + + blob = data_blob_talloc_zero(tctx, needed); + if (blob.data == NULL) { + return false; + } + + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "dcerpc_spoolss_EnumPrinters failed"); + torture_assert_werr_ok(tctx, r.out.result, + "dcerpc_spoolss_EnumPrinters failed"); + + *num_printers = count; + + return true; +} + +static bool getprinterinfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, int level, + union spoolss_PrinterInfo **res) +{ + struct spoolss_GetPrinter r; + DATA_BLOB blob; + uint32_t needed; + + r.in.handle = handle; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinter_r(b, tctx, &r), + "dcerpc_spoolss_GetPrinter failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "GetPrinter unexpected return code should be WERR_INSUFFICIENT_BUFFER"); + + r.in.handle = handle; + r.in.level = level; + blob = data_blob_talloc_zero(tctx, needed); + if (blob.data == NULL) { + return false; + } + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinter_r(b, tctx, &r), + "dcerpc_spoolss_GetPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, + "dcerpc_spoolss_GetPrinter failed"); + + if (res != NULL) { + *res = talloc_steal(tctx, r.out.info); + } + + return true; +} + +static bool torture_samba3_rpc_spoolss(struct torture_context *torture) +{ + struct dcerpc_pipe *p, *p2; + struct dcerpc_binding_handle *b; + struct policy_handle server_handle, printer_handle; + const char **printers; + size_t num_printers; + struct spoolss_UserLevel1 userlevel1; + char *servername; + + torture_assert(torture, + rap_get_servername(torture, &servername), + "failed to rap servername"); + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p2, &ndr_table_srvsvc), + "failed to setup srvsvc"); + + torture_assert(torture, + find_printers(torture, p2, &printers, &num_printers), + "failed to find printers via srvsvc"); + + talloc_free(p2); + + if (num_printers == 0) { + torture_skip(torture, "Did not find printers\n"); + return true; + } + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_spoolss), + "failed to setup spoolss"); + + b = p->binding_handle; + + ZERO_STRUCT(userlevel1); + userlevel1.client = talloc_asprintf( + torture, "\\\\%s", lpcfg_netbios_name(torture->lp_ctx)); + userlevel1.user = cli_credentials_get_username( + samba_cmdline_get_creds()); + userlevel1.build = 2600; + userlevel1.major = 3; + userlevel1.minor = 0; + userlevel1.processor = 0; + + { + struct spoolss_OpenPrinterEx r; + + ZERO_STRUCT(r); + r.in.printername = talloc_asprintf(torture, "\\\\%s", + servername); + r.in.datatype = NULL; + r.in.access_mask = 0; + r.in.userlevel_ctr.level = 1; + r.in.userlevel_ctr.user_info.level1 = &userlevel1; + r.out.handle = &server_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_spoolss_OpenPrinterEx_r(b, torture, &r), + "dcerpc_spoolss_OpenPrinterEx failed"); + torture_assert_werr_ok(torture, r.out.result, + "dcerpc_spoolss_OpenPrinterEx failed"); + } + + { + struct spoolss_ClosePrinter r; + + r.in.handle = &server_handle; + r.out.handle = &server_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_spoolss_ClosePrinter_r(b, torture, &r), + "dcerpc_spoolss_ClosePrinter failed"); + torture_assert_werr_ok(torture, r.out.result, + "dcerpc_spoolss_ClosePrinter failed"); + } + + { + struct spoolss_OpenPrinterEx r; + + ZERO_STRUCT(r); + r.in.printername = talloc_asprintf( + torture, "\\\\%s\\%s", servername, printers[0]); + r.in.datatype = NULL; + r.in.access_mask = 0; + r.in.userlevel_ctr.level = 1; + r.in.userlevel_ctr.user_info.level1 = &userlevel1; + r.out.handle = &printer_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_spoolss_OpenPrinterEx_r(b, torture, &r), + "dcerpc_spoolss_OpenPrinterEx failed"); + torture_assert_werr_ok(torture, r.out.result, + "dcerpc_spoolss_OpenPrinterEx failed"); + } + + { + int i; + + for (i=0; i<8; i++) { + torture_assert(torture, + getprinterinfo(torture, b, &printer_handle, i, NULL), + talloc_asprintf(torture, "getprinterinfo %d failed", i)); + } + } + + { + struct spoolss_ClosePrinter r; + + r.in.handle = &printer_handle; + r.out.handle = &printer_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_spoolss_ClosePrinter_r(b, torture, &r), + "dcerpc_spoolss_ClosePrinter failed"); + torture_assert_werr_ok(torture, r.out.result, + "dcerpc_spoolss_ClosePrinter failed"); + } + + { + int num_enumerated; + + torture_assert(torture, + enumprinters(torture, b, servername, 1, &num_enumerated), + "enumprinters failed"); + + torture_assert_int_equal(torture, num_printers, num_enumerated, + "netshareenum / enumprinters lvl 1 numprinter mismatch"); + } + + { + int num_enumerated; + + torture_assert(torture, + enumprinters(torture, b, servername, 2, &num_enumerated), + "enumprinters failed"); + + torture_assert_int_equal(torture, num_printers, num_enumerated, + "netshareenum / enumprinters lvl 2 numprinter mismatch"); + } + + return true; +} + +static bool torture_samba3_rpc_wkssvc(struct torture_context *torture) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + char *servername; + + torture_assert(torture, + rap_get_servername(torture, &servername), + "failed to rap servername"); + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_wkssvc), + "failed to setup wkssvc"); + + b = p->binding_handle; + + { + struct wkssvc_NetWkstaInfo100 wks100; + union wkssvc_NetWkstaInfo info; + struct wkssvc_NetWkstaGetInfo r; + + r.in.server_name = "\\foo"; + r.in.level = 100; + info.info100 = &wks100; + r.out.info = &info; + + torture_assert_ntstatus_ok(torture, + dcerpc_wkssvc_NetWkstaGetInfo_r(b, torture, &r), + "dcerpc_wkssvc_NetWksGetInfo failed"); + torture_assert_werr_ok(torture, r.out.result, + "dcerpc_wkssvc_NetWksGetInfo failed"); + + torture_assert_str_equal(torture, servername, r.out.info->info100->server_name, + "servername RAP / DCERPC inconsistency"); + } + + return true; +} + +static bool winreg_close(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct winreg_CloseKey c; + + c.in.handle = c.out.handle = handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_CloseKey_r(b, tctx, &c), + "winreg_CloseKey failed"); + torture_assert_werr_ok(tctx, c.out.result, + "winreg_CloseKey failed"); + + return true; +} + +static bool enumvalues(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + uint32_t enum_index = 0; + + while (1) { + struct winreg_EnumValue r; + struct winreg_ValNameBuf name; + enum winreg_Type type = 0; + uint8_t buf8[1024]; + NTSTATUS status; + uint32_t size, length; + + ZERO_STRUCT(buf8); + r.in.handle = handle; + r.in.enum_index = enum_index; + name.name = ""; + name.size = 1024; + r.in.name = r.out.name = &name; + size = 1024; + length = 5; + r.in.type = &type; + r.in.value = buf8; + r.in.size = &size; + r.in.length = &length; + + status = dcerpc_winreg_EnumValue_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status) || !W_ERROR_IS_OK(r.out.result)) { + return true; + } + enum_index += 1; + } +} + +static bool enumkeys(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + int depth) +{ + struct winreg_EnumKey r; + struct winreg_StringBuf kclass, name; + NTSTATUS status; + NTTIME t = 0; + + if (depth <= 0) { + return true; + } + + kclass.name = ""; + kclass.size = 1024; + + r.in.handle = handle; + r.in.enum_index = 0; + r.in.name = &name; + r.in.keyclass = &kclass; + r.out.name = &name; + r.in.last_changed_time = &t; + + do { + struct winreg_OpenKey o; + struct policy_handle key_handle; + int i; + + name.name = NULL; + name.size = 1024; + + status = dcerpc_winreg_EnumKey_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status) || !W_ERROR_IS_OK(r.out.result)) { + /* We're done enumerating */ + return true; + } + + for (i=0; i<10-depth; i++) { + torture_comment(tctx, " "); + } + torture_comment(tctx, "%s\n", r.out.name->name); + + o.in.parent_handle = handle; + o.in.keyname.name = r.out.name->name; + o.in.options = 0; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &key_handle; + + status = dcerpc_winreg_OpenKey_r(b, tctx, &o); + if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(o.out.result)) { + enumkeys(tctx, b, &key_handle, depth-1); + enumvalues(tctx, b, &key_handle); + torture_assert(tctx, winreg_close(tctx, b, &key_handle), ""); + } + + r.in.enum_index += 1; + } while(true); + + return true; +} + +typedef NTSTATUS (*winreg_open_fn)(struct dcerpc_binding_handle *, TALLOC_CTX *, void *); + +static bool test_Open3(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *name, winreg_open_fn open_fn) +{ + struct policy_handle handle; + struct winreg_OpenHKLM r; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, + open_fn(b, tctx, &r), + talloc_asprintf(tctx, "%s failed", name)); + torture_assert_werr_ok(tctx, r.out.result, + talloc_asprintf(tctx, "%s failed", name)); + + enumkeys(tctx, b, &handle, 4); + + torture_assert(tctx, + winreg_close(tctx, b, &handle), + "dcerpc_CloseKey failed"); + + return true; +} + +static bool torture_samba3_rpc_winreg(struct torture_context *torture) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + bool ret = true; + struct { + const char *name; + winreg_open_fn fn; + } open_fns[] = { + {"OpenHKLM", (winreg_open_fn)dcerpc_winreg_OpenHKLM_r }, + {"OpenHKU", (winreg_open_fn)dcerpc_winreg_OpenHKU_r }, + {"OpenHKPD", (winreg_open_fn)dcerpc_winreg_OpenHKPD_r }, + {"OpenHKPT", (winreg_open_fn)dcerpc_winreg_OpenHKPT_r }, + {"OpenHKCR", (winreg_open_fn)dcerpc_winreg_OpenHKCR_r }}; +#if 0 + int i; +#endif + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_winreg), + "failed to setup winreg"); + + b = p->binding_handle; + +#if 1 + ret = test_Open3(torture, b, open_fns[0].name, open_fns[0].fn); +#else + for (i = 0; i < ARRAY_SIZE(open_fns); i++) { + if (!test_Open3(torture, b, open_fns[i].name, open_fns[i].fn)) + ret = false; + } +#endif + return ret; +} + +static bool get_shareinfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, + const char *share, + struct srvsvc_NetShareInfo502 **info502) +{ + struct srvsvc_NetShareGetInfo r; + union srvsvc_NetShareInfo info; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", servername); + r.in.share_name = share; + r.in.level = 502; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_srvsvc_NetShareGetInfo_r(b, tctx, &r), + "srvsvc_NetShareGetInfo failed"); + torture_assert_werr_ok(tctx, r.out.result, + "srvsvc_NetShareGetInfo failed"); + + *info502 = talloc_move(tctx, &info.info502); + + return true; +} + +/* + * Get us a handle on HKLM\ + */ + +static bool get_hklm_handle(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct winreg_OpenHKLM r; + struct policy_handle result; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &result; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenHKLM_r(b, tctx, &r), + "OpenHKLM failed"); + torture_assert_werr_ok(tctx, r.out.result, + "OpenHKLM failed"); + + *handle = result; + + return true; +} + +static bool torture_samba3_createshare(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *sharename) +{ + struct policy_handle hklm; + struct policy_handle new_handle; + struct winreg_CreateKey c; + struct winreg_CloseKey cl; + enum winreg_CreateAction action_taken = REG_ACTION_NONE; + + ZERO_STRUCT(c); + ZERO_STRUCT(cl); + ZERO_STRUCT(hklm); + ZERO_STRUCT(new_handle); + + c.in.handle = &hklm; + c.in.name.name = talloc_asprintf( + tctx, "software\\samba\\smbconf\\%s", sharename); + torture_assert(tctx, c.in.name.name, "talloc_asprintf failed"); + + c.in.keyclass.name = ""; + c.in.options = 0; + c.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + c.in.secdesc = NULL; + c.in.action_taken = &action_taken; + c.out.new_handle = &new_handle; + c.out.action_taken = &action_taken; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_CreateKey_r(b, tctx, &c), + "OpenKey failed"); + torture_assert_werr_ok(tctx, c.out.result, + "OpenKey failed"); + + cl.in.handle = &new_handle; + cl.out.handle = &new_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_CloseKey_r(b, tctx, &cl), + "CloseKey failed"); + torture_assert_werr_ok(tctx, cl.out.result, + "CloseKey failed"); + + return true; +} + +static bool torture_samba3_deleteshare(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *sharename) +{ + struct policy_handle hklm; + struct winreg_DeleteKey d; + + torture_assert(tctx, + get_hklm_handle(tctx, b, &hklm), + "get_hklm_handle failed"); + + d.in.handle = &hklm; + d.in.key.name = talloc_asprintf( + tctx, "software\\samba\\smbconf\\%s", sharename); + torture_assert(tctx, d.in.key.name, "talloc_asprintf failed"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_DeleteKey_r(b, tctx, &d), + "DeleteKey failed"); + torture_assert_werr_ok(tctx, d.out.result, + "DeleteKey failed"); + + return true; +} + +static bool torture_samba3_setconfig(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *sharename, + const char *parameter, + const char *value) +{ + struct policy_handle hklm, key_handle; + struct winreg_OpenKey o; + struct winreg_SetValue s; + uint32_t type; + DATA_BLOB val; + + torture_assert(tctx, + get_hklm_handle(tctx, b, &hklm), + "get_hklm_handle failed"); + + o.in.parent_handle = &hklm; + o.in.keyname.name = talloc_asprintf( + tctx, "software\\samba\\smbconf\\%s", sharename); + torture_assert(tctx, o.in.keyname.name, "talloc_asprintf failed"); + + o.in.options = 0; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &key_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenKey_r(b, tctx, &o), + "OpenKey failed"); + torture_assert_werr_ok(tctx, o.out.result, + "OpenKey failed"); + + torture_assert(tctx, + reg_string_to_val(tctx, "REG_SZ", value, &type, &val), + "reg_string_to_val failed"); + + s.in.handle = &key_handle; + s.in.name.name = parameter; + s.in.type = type; + s.in.data = val.data; + s.in.size = val.length; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_SetValue_r(b, tctx, &s), + "SetValue failed"); + torture_assert_werr_ok(tctx, s.out.result, + "SetValue failed"); + + return true; +} + +static bool torture_samba3_regconfig(struct torture_context *torture) +{ + struct srvsvc_NetShareInfo502 *i = NULL; + const char *comment = "Dummer Kommentar"; + struct dcerpc_pipe *srvsvc_pipe, *winreg_pipe; + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &srvsvc_pipe, &ndr_table_srvsvc), + "failed to setup srvsvc"); + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &winreg_pipe, &ndr_table_winreg), + "failed to setup winreg"); + + torture_assert(torture, + torture_samba3_createshare(torture, winreg_pipe->binding_handle, "blubber"), + "torture_samba3_createshare failed"); + + torture_assert(torture, + torture_samba3_setconfig(torture, winreg_pipe->binding_handle, "blubber", "comment", comment), + "torture_samba3_setconfig failed"); + + torture_assert(torture, + get_shareinfo(torture, srvsvc_pipe->binding_handle, dcerpc_server_name(srvsvc_pipe), "blubber", &i), + "get_shareinfo failed"); + + torture_assert_str_equal(torture, comment, i->comment, + "got unexpected comment"); + + torture_assert(torture, + torture_samba3_deleteshare(torture, winreg_pipe->binding_handle, "blubber"), + "torture_samba3_deleteshare failed"); + + return true; +} + +/* + * Test that even with a result of 0 rids the array is returned as a + * non-NULL pointer. Yes, XP does notice. + */ + +bool torture_samba3_getaliasmembership_0(struct torture_context *torture) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + struct samr_Connect2 c; + struct samr_OpenDomain o; + struct dom_sid sid; + struct lsa_SidPtr ptr; + struct lsa_SidArray sids; + struct samr_GetAliasMembership g; + struct samr_Ids rids; + struct policy_handle samr, domain; + + torture_assert_ntstatus_ok(torture, + torture_rpc_connection(torture, &p, &ndr_table_samr), + "failed to setup samr"); + + b = p->binding_handle; + + c.in.system_name = NULL; + c.in.access_mask = SAMR_ACCESS_LOOKUP_DOMAIN; + c.out.connect_handle = &samr; + torture_assert_ntstatus_ok(torture, + dcerpc_samr_Connect2_r(b, torture, &c), + ""); + torture_assert_ntstatus_ok(torture, c.out.result, + ""); + dom_sid_parse("S-1-5-32", &sid); + o.in.connect_handle = &samr; + o.in.access_mask = SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS; + o.in.sid = &sid; + o.out.domain_handle = &domain; + torture_assert_ntstatus_ok(torture, + dcerpc_samr_OpenDomain_r(b, torture, &o), + ""); + torture_assert_ntstatus_ok(torture, o.out.result, + ""); + dom_sid_parse("S-1-2-3-4-5", &sid); + ptr.sid = &sid; + sids.num_sids = 1; + sids.sids = &ptr; + g.in.domain_handle = &domain; + g.in.sids = &sids; + g.out.rids = &rids; + torture_assert_ntstatus_ok(torture, + dcerpc_samr_GetAliasMembership_r(b, torture, &g), + ""); + torture_assert_ntstatus_ok(torture, g.out.result, + ""); + if (rids.ids == NULL) { + /* This is the piece to test here */ + torture_fail(torture, + "torture_samba3_getaliasmembership_0: " + "Server returns NULL rids array\n"); + } + + return true; +} + +/** + * Test smb reauthentication while rpc pipe is in use. + */ +static bool torture_rpc_smb_reauth1(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_state *cli; + struct smbcli_options options; + struct smbcli_session_options session_options; + + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_GetUserName r; + struct lsa_String *authority_name_p = NULL; + char *authority_name_saved = NULL; + struct lsa_String *account_name_p = NULL; + char *account_name_saved = NULL; + struct cli_credentials *anon_creds = NULL; + struct smb_composite_sesssetup io; + + mem_ctx = talloc_init("torture_samba3_reauth"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(mem_ctx, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smbcli_full_connection failed"); + + status = pipe_bind_smb(torture, mem_ctx, cli->tree, "\\lsarpc", + &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb failed"); + lsa_handle = lsa_pipe->binding_handle; + + /* lsa getusername */ + + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_comment(torture, "lsa_GetUserName gave '%s\\%s'\n", + authority_name_p->string, + account_name_p->string); + + account_name_saved = talloc_strdup(mem_ctx, account_name_p->string); + torture_assert_goto(torture, (account_name_saved != NULL), ret, done, + "talloc failed"); + authority_name_saved = talloc_strdup(mem_ctx, authority_name_p->string); + torture_assert_goto(torture, (authority_name_saved != NULL), ret, done, + "talloc failed"); + + /* smb re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + + ZERO_STRUCT(io); + io.in.sesskey = cli->transport->negotiate.sesskey; + io.in.capabilities = cli->transport->negotiate.capabilities; + io.in.credentials = anon_creds; + io.in.workgroup = lpcfg_workgroup(torture->lp_ctx); + io.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(cli->session, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername after reauth */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + /* smb re-auth again to the original user */ + + ZERO_STRUCT(io); + io.in.sesskey = cli->transport->negotiate.sesskey; + io.in.capabilities = cli->transport->negotiate.capabilities; + io.in.credentials = samba_cmdline_get_creds(); + io.in.workgroup = lpcfg_workgroup(torture->lp_ctx); + io.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(cli->session, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + ret = true; + +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test smb reauthentication while rpc pipe is in use. + * Open a second lsa bind after reauth to anon. + * Do lsa getusername on that second bind. + */ +static bool torture_rpc_smb_reauth2(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_state *cli; + struct smbcli_options options; + struct smbcli_session_options session_options; + + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_GetUserName r; + struct lsa_String *authority_name_p = NULL; + char *authority_name_saved = NULL; + struct lsa_String *account_name_p = NULL; + char *account_name_saved = NULL; + struct cli_credentials *anon_creds = NULL; + struct smb_composite_sesssetup io; + + mem_ctx = talloc_init("torture_samba3_reauth"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(mem_ctx, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smbcli_full_connection failed"); + + /* smb re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + + ZERO_STRUCT(io); + io.in.sesskey = cli->transport->negotiate.sesskey; + io.in.capabilities = cli->transport->negotiate.capabilities; + io.in.credentials = anon_creds; + io.in.workgroup = lpcfg_workgroup(torture->lp_ctx); + io.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(cli->session, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* open the lsa pipe */ + + status = pipe_bind_smb(torture, mem_ctx, cli->tree, "\\lsarpc", + &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb failed"); + lsa_handle = lsa_pipe->binding_handle; + + /* lsa getusername */ + + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_comment(torture, "lsa_GetUserName gave '%s\\%s'\n", + authority_name_p->string, + account_name_p->string); + + account_name_saved = talloc_strdup(mem_ctx, account_name_p->string); + torture_assert_goto(torture, (account_name_saved != NULL), ret, done, + "talloc failed"); + authority_name_saved = talloc_strdup(mem_ctx, authority_name_p->string); + torture_assert_goto(torture, (authority_name_saved != NULL), ret, done, + "talloc failed"); + + /* smb re-auth again to the original user */ + + ZERO_STRUCT(io); + io.in.sesskey = cli->transport->negotiate.sesskey; + io.in.capabilities = cli->transport->negotiate.capabilities; + io.in.credentials = samba_cmdline_get_creds(); + io.in.workgroup = lpcfg_workgroup(torture->lp_ctx); + io.in.gensec_settings = lpcfg_gensec_settings(torture, torture->lp_ctx); + + status = smb_composite_sesssetup(cli->session, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername after reauth */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + ret = true; + +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test smb2 reauthentication while rpc pipe is in use. + */ +static bool torture_rpc_smb2_reauth1(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_GetUserName r; + struct lsa_String *authority_name_p = NULL; + char *authority_name_saved = NULL; + struct lsa_String *account_name_p = NULL; + char *account_name_saved = NULL; + struct cli_credentials *anon_creds = NULL; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + + mem_ctx = talloc_init("torture_samba3_reauth"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + status = pipe_bind_smb2(torture, mem_ctx, tree, "lsarpc", + &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb2 failed"); + lsa_handle = lsa_pipe->binding_handle; + + /* lsa getusername */ + + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_comment(torture, "lsa_GetUserName gave '%s\\%s'\n", + authority_name_p->string, + account_name_p->string); + + account_name_saved = talloc_strdup(mem_ctx, account_name_p->string); + torture_assert_goto(torture, (account_name_saved != NULL), ret, done, + "talloc failed"); + authority_name_saved = talloc_strdup(mem_ctx, authority_name_p->string); + torture_assert_goto(torture, (authority_name_saved != NULL), ret, done, + "talloc failed"); + + /* smb re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername after reauth */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + /* smb re-auth again to the original user */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + ret = true; + +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test smb2 reauthentication while rpc pipe is in use. + * Open a second lsa bind after reauth to anon. + * Do lsa getusername on that second bind. + */ +static bool torture_rpc_smb2_reauth2(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + + struct dcerpc_pipe *lsa_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_GetUserName r; + struct lsa_String *authority_name_p = NULL; + char *authority_name_saved = NULL; + struct lsa_String *account_name_p = NULL; + char *account_name_saved = NULL; + struct cli_credentials *anon_creds = NULL; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + + mem_ctx = talloc_init("torture_samba3_reauth"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + /* smb re-authenticate as anonymous */ + + anon_creds = cli_credentials_init_anon(mem_ctx); + + status = smb2_session_setup_spnego(tree->session, + anon_creds, + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* open the lsa pipe */ + + status = pipe_bind_smb2(torture, mem_ctx, tree, "lsarpc", + &ndr_table_lsarpc, &lsa_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb2 failed"); + lsa_handle = lsa_pipe->binding_handle; + + /* lsa getusername */ + + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_comment(torture, "lsa_GetUserName gave '%s\\%s'\n", + authority_name_p->string, + account_name_p->string); + + account_name_saved = talloc_strdup(mem_ctx, account_name_p->string); + torture_assert_goto(torture, (account_name_saved != NULL), ret, done, + "talloc failed"); + authority_name_saved = talloc_strdup(mem_ctx, authority_name_p->string); + torture_assert_goto(torture, (authority_name_saved != NULL), ret, done, + "talloc failed"); + + /* smb re-auth again to the original user */ + + status = smb2_session_setup_spnego(tree->session, + samba_cmdline_get_creds(), + 0 /* previous_session_id */); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "session reauth to anon failed"); + + /* re-do lsa getusername */ + + TALLOC_FREE(authority_name_p); + TALLOC_FREE(account_name_p); + ZERO_STRUCT(r); + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + status = dcerpc_lsa_GetUserName_r(lsa_handle, mem_ctx, &r); + + authority_name_p = *r.out.authority_name; + + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "GetUserName failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, ret, done, + "GetUserName failed"); + + torture_assert_goto(torture, (strcmp(authority_name_p->string, authority_name_saved) == 0), + ret, done, "authority_name not equal after reauth to anon"); + torture_assert_goto(torture, (strcmp(account_name_p->string, account_name_saved) == 0), + ret, done, "account_name not equal after reauth to anon"); + + ret = true; + +done: + talloc_free(mem_ctx); + return ret; +} + +static bool torture_rpc_smb1_pipe_name(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_state *cli; + struct smbcli_options options; + struct smbcli_session_options session_options; + union smb_open io; + union smb_close cl; + uint16_t fnum; + + mem_ctx = talloc_init("torture_samba3_smb1_pipe_name"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + lpcfg_smbcli_session_options(torture->lp_ctx, &session_options); + + status = smbcli_full_connection(mem_ctx, &cli, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", NULL, + lpcfg_socket_options(torture->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(torture->lp_ctx), + torture->ev, &options, &session_options, + lpcfg_gensec_settings(torture, torture->lp_ctx)); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smbcli_full_connection failed"); + + ZERO_STRUCT(io); + io.generic.level = RAW_OPEN_NTCREATEX; + io.ntcreatex.in.access_mask = DESIRED_ACCESS_PIPE; + io.ntcreatex.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.ntcreatex.in.open_disposition = NTCREATEX_DISP_OPEN; + io.ntcreatex.in.impersonation = NTCREATEX_IMPERSONATION_IMPERSONATION; + io.ntcreatex.in.security_flags = 0; + + io.ntcreatex.in.fname = "__none__"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for '__none__'"); + + io.ntcreatex.in.fname = "pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for 'pipe\\srvsvc'"); + + io.ntcreatex.in.fname = "\\pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for '\\pipe\\srvsvc'"); + + io.ntcreatex.in.fname = "srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'srvsvc'"); + fnum = io.ntcreatex.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.ntcreatex.in.fname = "\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\srvsvc'"); + fnum = io.ntcreatex.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.ntcreatex.in.fname = "\\\\\\\\\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\\\\\\\\\srvsvc'"); + fnum = io.ntcreatex.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + ZERO_STRUCT(io); + io.generic.level = RAW_OPEN_NTTRANS_CREATE; + io.nttrans.in.access_mask = DESIRED_ACCESS_PIPE; + io.nttrans.in.share_access = NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.nttrans.in.open_disposition = NTCREATEX_DISP_OPEN; + io.nttrans.in.impersonation = NTCREATEX_IMPERSONATION_IMPERSONATION; + io.nttrans.in.security_flags = 0; + + io.nttrans.in.fname = "__none__"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for '__none__'"); + + io.nttrans.in.fname = "pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for 'pipe\\srvsvc'"); + + io.nttrans.in.fname = "\\pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for '\\pipe\\srvsvc'"); + + io.nttrans.in.fname = "srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'srvsvc'"); + fnum = io.nttrans.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.nttrans.in.fname = "\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\srvsvc'"); + fnum = io.nttrans.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.nttrans.in.fname = "\\\\\\\\\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\\\\\\\\\srvsvc'"); + fnum = io.nttrans.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + ZERO_STRUCT(io); + io.generic.level = RAW_OPEN_OPENX; + io.openx.in.open_mode = OPENX_MODE_ACCESS_RDWR; + io.openx.in.open_func = OPENX_OPEN_FUNC_OPEN; + + io.openx.in.fname = "__none__"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, + ret, done, + "smb_raw_open for '__none__'"); + + io.openx.in.fname = "srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, + ret, done, + "smb_raw_open for 'srvsvc'"); + + io.openx.in.fname = "\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, + ret, done, + "smb_raw_open for '\\srvsvc'"); + + io.openx.in.fname = "\\pipesrvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD, + ret, done, + "smb_raw_open for '\\pipesrvsvc'"); + + io.openx.in.fname = "pipe\\__none__"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for 'pipe\\__none__'"); + + io.openx.in.fname = "\\pipe\\__none__"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb_raw_open for '\\pipe\\__none__'"); + + io.openx.in.fname = "pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'pipe\\srvsvc'"); + fnum = io.openx.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.openx.in.fname = "\\pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\pipe\\srvsvc'"); + fnum = io.openx.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.openx.in.fname = "\\\\\\\\\\pipe\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\\\\\\\\\pipe\\srvsvc'"); + fnum = io.openx.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + + io.openx.in.fname = "\\\\\\\\\\pipe\\\\\\\\\\srvsvc"; + status = smb_raw_open(cli->tree, torture, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for '\\\\\\\\\\pipe\\\\\\\\\\srvsvc'"); + fnum = io.openx.out.file.fnum; + ZERO_STRUCT(cl); + cl.generic.level = RAW_CLOSE_CLOSE; + cl.close.in.file.fnum = fnum; + status = smb_raw_close(cli->tree, &cl); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb_raw_close failed"); + ret = true; + +done: + talloc_free(mem_ctx); + return ret; +} + +static bool torture_rpc_smb2_pipe_name(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + struct smb2_handle h; + struct smb2_create io; + + mem_ctx = talloc_init("torture_samba3_smb2_pipe_name"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = DESIRED_ACCESS_PIPE; + io.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + + io.in.fname = "__none__"; + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create for '__none__'"); + + io.in.fname = "\\srvsvc"; + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create for '\\srvsvc'"); + + io.in.fname = "\\pipe\\srvsvc"; + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create for '\\pipe\\srvsvc'"); + + io.in.fname = "pipe\\srvsvc"; + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_OBJECT_NAME_NOT_FOUND, + ret, done, + "smb2_create for 'pipe\\srvsvc'"); + + io.in.fname = "srvsvc"; + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'srvsvc'"); + + h = io.out.file.handle; + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_util_close failed"); + + ret = true; +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test behaviour of a waiting read call on a pipe when + * the pipe handle is closed: + * - open a pipe via smb2 + * - trigger a read which hangs since there is nothing to read + * - close the pipe file handle + * - wait for the read to return and check the status + * (STATUS_PIPE_BROKEN) + */ +static bool torture_rpc_smb2_pipe_read_close(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + struct smb2_handle h; + struct smb2_request *smb2req; + struct smb2_create io; + struct smb2_read rd; + + mem_ctx = talloc_init("torture_samba3_pipe_read_close"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = DESIRED_ACCESS_PIPE; + io.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = "lsarpc"; + + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'lsarpc'"); + + h = io.out.file.handle; + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 1024; + rd.in.offset = 0; + rd.in.min_count = 0; + + smb2req = smb2_read_send(tree, &rd); + torture_assert_goto(torture, (smb2req != NULL), ret, done, + "smb2_read_send failed"); + + status = smb2_util_close(tree, h); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_util_close failed"); + + status = smb2_read_recv(smb2req, mem_ctx, &rd); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_PIPE_BROKEN, ret, done, + "smb2_read_recv: unexpected return code"); + + ret = true; +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test behaviour of a waiting read call on a pipe when + * the tree is disconnected. + * - open a pipe via smb2 + * - trigger a read which hangs since there is nothing to read + * - do a tree disconnect + * - wait for the read to return and check the status + * (STATUS_PIPE_BROKEN) + */ +static bool torture_rpc_smb2_pipe_read_tdis(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + struct smb2_handle h; + struct smb2_request *smb2req; + struct smb2_create io; + struct smb2_read rd; + + mem_ctx = talloc_init("torture_samba3_pipe_read_tdis"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = DESIRED_ACCESS_PIPE; + io.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = "lsarpc"; + + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'lsarpc'"); + + h = io.out.file.handle; + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 1024; + rd.in.offset = 0; + rd.in.min_count = 0; + + smb2req = smb2_read_send(tree, &rd); + torture_assert_goto(torture, (smb2req != NULL), ret, done, + "smb2_read_send failed"); + + status = smb2_tdis(tree); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_tdis failed"); + + status = smb2_read_recv(smb2req, mem_ctx, &rd); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_PIPE_BROKEN, ret, done, + "smb2_read_recv: unexpected return code"); + + ret = true; +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Test behaviour of a waiting read call on a pipe when + * the user logs off + * - open a pipe via smb2 + * - trigger a read which hangs since there is nothing to read + * - do a logoff + * - wait for the read to return and check the status + * (STATUS_PIPE_BROKEN) + */ +static bool torture_rpc_smb2_pipe_read_logoff(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + const char *host = torture_setting_string(torture, "host", NULL); + struct smb2_tree *tree; + struct smb2_handle h; + struct smb2_request *smb2req; + struct smb2_create io; + struct smb2_read rd; + + mem_ctx = talloc_init("torture_samba3_pipe_read_tdis"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + host, + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = DESIRED_ACCESS_PIPE; + io.in.impersonation_level = SMB2_IMPERSONATION_IMPERSONATION; + io.in.file_attributes = 0; + io.in.create_disposition = NTCREATEX_DISP_OPEN; + io.in.share_access = + NTCREATEX_SHARE_ACCESS_READ| + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = "lsarpc"; + + status = smb2_create(tree, tree, &io); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_create failed for 'lsarpc'"); + + h = io.out.file.handle; + + ZERO_STRUCT(rd); + rd.in.file.handle = h; + rd.in.length = 1024; + rd.in.offset = 0; + rd.in.min_count = 0; + + smb2req = smb2_read_send(tree, &rd); + torture_assert_goto(torture, (smb2req != NULL), ret, done, + "smb2_read_send failed"); + + status = smb2_logoff(tree->session); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_logoff failed"); + + status = smb2_read_recv(smb2req, mem_ctx, &rd); + torture_assert_ntstatus_equal_goto(torture, status, NT_STATUS_PIPE_BROKEN, ret, done, + "smb2_read_recv: unexpected return code"); + + ret = true; +done: + talloc_free(mem_ctx); + return ret; +} + +static bool torture_rpc_lsa_over_netlogon(struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + struct smb2_tree *tree; + struct dcerpc_pipe *netlogon_pipe; + struct dcerpc_binding_handle *lsa_handle; + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 o; + struct policy_handle handle; + + torture_comment(torture, "Testing if we can access LSA server over " + "\\\\pipe\\netlogon rather than \\\\pipe\\lsarpc\n"); + + mem_ctx = talloc_init("torture_samba3_rpc_lsa_over_netlogon"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + status = pipe_bind_smb2(torture, mem_ctx, tree, "netlogon", + &ndr_table_lsarpc, &netlogon_pipe); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "pipe_bind_smb2 failed"); + lsa_handle = netlogon_pipe->binding_handle; + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + o.in.system_name = "\\"; + o.in.attr = &attr; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.out.handle = &handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_lsa_OpenPolicy2_r(lsa_handle, torture, &o), + "OpenPolicy2 failed"); + torture_assert_ntstatus_ok(torture, + o.out.result, + "OpenPolicy2 failed"); + + ret = true; + done: + talloc_free(mem_ctx); + return ret; +} + +static bool torture_rpc_pipes_supported_interfaces( + struct torture_context *torture) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS status; + bool ret = false; + struct smbcli_options options; + struct smb2_tree *tree; + struct dcerpc_pipe *pipe1; + struct dcerpc_pipe *pipe2; + struct dcerpc_pipe *pipe3; + + torture_comment(torture, "Testing only appropriate interfaces are " + "available in smb pipes\n"); + + mem_ctx = talloc_init("torture_samba3_rpc_pipes_supported_interfaces"); + torture_assert(torture, (mem_ctx != NULL), "talloc_init failed"); + + lpcfg_smbcli_options(torture->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + torture_setting_string(torture, "host", NULL), + lpcfg_smb_ports(torture->lp_ctx), + "IPC$", + lpcfg_resolve_context(torture->lp_ctx), + samba_cmdline_get_creds(), + &tree, + torture->ev, + &options, + lpcfg_socket_options(torture->lp_ctx), + lpcfg_gensec_settings(torture, torture->lp_ctx) + ); + torture_assert_ntstatus_ok_goto(torture, status, ret, done, + "smb2_connect failed"); + + /* Test embedded services pipes. The svcctl interface is + * not available if we open the winreg pipe. */ + status = pipe_bind_smb2(torture, mem_ctx, tree, "winreg", + &ndr_table_svcctl, &pipe1); + torture_assert_ntstatus_equal(torture, + status, + NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX, + "svcctl interface not supported in winreg pipe"); + + /* Test it is not possible to bind to S4 server provided services */ + status = pipe_bind_smb2(torture, mem_ctx, tree, "srvsvc", + &ndr_table_samr, &pipe2); + torture_assert_ntstatus_equal(torture, + status, + NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX, + "samr interface not supported in srvsvc pipe"); + + /* Test pipes in forked daemons like lsassd. The lsarpc interface is + * not available if we open the SAMR pipe. */ + status = pipe_bind_smb2(torture, mem_ctx, tree, "samr", + &ndr_table_lsarpc, &pipe3); + torture_assert_ntstatus_equal(torture, + status, + NT_STATUS_RPC_UNSUPPORTED_NAME_SYNTAX, + "lsarpc interface not supported in samr pipe"); + + ret = true; + done: + talloc_free(mem_ctx); + return ret; +} + +struct torture_suite *torture_rpc_samba3(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samba3"); + + torture_suite_add_simple_test(suite, "bind", torture_bind_samba3); + torture_suite_add_simple_test(suite, "netlogon", torture_netlogon_samba3); + torture_suite_add_simple_test(suite, "sessionkey", torture_samba3_sessionkey); + torture_suite_add_simple_test(suite, "srvsvc", torture_samba3_rpc_srvsvc); + torture_suite_add_simple_test(suite, "sharesec", torture_samba3_rpc_sharesec); + torture_suite_add_simple_test(suite, "getusername", torture_samba3_rpc_getusername); + torture_suite_add_simple_test(suite, "randomauth2", torture_samba3_rpc_randomauth2); + torture_suite_add_simple_test(suite, "lsa", torture_samba3_rpc_lsa); + torture_suite_add_simple_test(suite, "spoolss", torture_samba3_rpc_spoolss); + torture_suite_add_simple_test(suite, "wkssvc", torture_samba3_rpc_wkssvc); + torture_suite_add_simple_test(suite, "winreg", torture_samba3_rpc_winreg); + torture_suite_add_simple_test(suite, "getaliasmembership-0", torture_samba3_getaliasmembership_0); + torture_suite_add_simple_test(suite, "regconfig", torture_samba3_regconfig); + torture_suite_add_simple_test(suite, "smb-reauth1", torture_rpc_smb_reauth1); + torture_suite_add_simple_test(suite, "smb-reauth2", torture_rpc_smb_reauth2); + torture_suite_add_simple_test(suite, "smb2-reauth1", torture_rpc_smb2_reauth1); + torture_suite_add_simple_test(suite, "smb2-reauth2", torture_rpc_smb2_reauth2); + torture_suite_add_simple_test(suite, "smb1-pipe-name", torture_rpc_smb1_pipe_name); + torture_suite_add_simple_test(suite, "smb2-pipe-name", torture_rpc_smb2_pipe_name); + torture_suite_add_simple_test(suite, "smb2-pipe-read-close", torture_rpc_smb2_pipe_read_close); + torture_suite_add_simple_test(suite, "smb2-pipe-read-tdis", torture_rpc_smb2_pipe_read_tdis); + torture_suite_add_simple_test(suite, "smb2-pipe-read-logoff", torture_rpc_smb2_pipe_read_logoff); + torture_suite_add_simple_test(suite, + "lsa_over_netlogon", + torture_rpc_lsa_over_netlogon); + torture_suite_add_simple_test(suite, + "pipes_supported_interfaces", + torture_rpc_pipes_supported_interfaces); + + suite->description = talloc_strdup(suite, "samba3 DCERPC interface tests"); + + return suite; +} diff --git a/source4/torture/rpc/samlogon.c b/source4/torture/rpc/samlogon.c new file mode 100644 index 0000000..00a87ba --- /dev/null +++ b/source4/torture/rpc/samlogon.c @@ -0,0 +1,2121 @@ +/* + Unix SMB/CIFS implementation. + + test suite for netlogon SamLogon operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004 + Copyright (C) Tim Potter 2003 + + 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_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "lib/cmdline/cmdline.h" +#include "torture/rpc/torture_rpc.h" +#include "auth/gensec/gensec.h" +#include "libcli/auth/libcli_auth.h" +#include "param/param.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#define TEST_MACHINE_NAME "samlogontest" +#define TEST_USER_NAME "samlogontestuser" +#define TEST_USER_NAME_WRONG_WKS "samlogontest2" +#define TEST_USER_NAME_WRONG_TIME "samlogontest3" + +enum ntlm_break { + BREAK_BOTH, + BREAK_NONE, + BREAK_LM, + BREAK_NT, + NO_LM, + NO_NT +}; + +struct samlogon_state { + TALLOC_CTX *mem_ctx; + struct torture_context *tctx; + const char *comment; + const char *account_name; + const char *account_domain; + const char *netbios_name; + const char *password; + const char *workgroup; + struct dcerpc_pipe *p; + int function_level; + uint32_t parameter_control; + struct netr_LogonSamLogon r; + struct netr_LogonSamLogonEx r_ex; + struct netr_LogonSamLogonWithFlags r_flags; + struct netr_Authenticator auth, auth2; + struct netlogon_creds_CredentialState *creds; + NTSTATUS expected_error; + bool old_password; /* Allow an old password to be accepted or rejected without error, as well as session key bugs */ + DATA_BLOB chall; +}; + +/* + Authenticate a user with a challenge/response, checking session key + and valid authentication types +*/ +static NTSTATUS check_samlogon(struct samlogon_state *samlogon_state, + enum ntlm_break break_which, + uint32_t parameter_control, + DATA_BLOB *chall, + DATA_BLOB *lm_response, + DATA_BLOB *nt_response, + uint8_t lm_key[8], + uint8_t user_session_key[16], + char **error_string) +{ + NTSTATUS status; + struct netr_LogonSamLogon *r = &samlogon_state->r; + struct netr_LogonSamLogonEx *r_ex = &samlogon_state->r_ex; + struct netr_LogonSamLogonWithFlags *r_flags = &samlogon_state->r_flags; + struct netr_NetworkInfo ninfo; + struct netr_SamBaseInfo *base = NULL; + uint16_t validation_level = 0; + + samlogon_state->r.in.logon->network = &ninfo; + samlogon_state->r_ex.in.logon->network = &ninfo; + samlogon_state->r_flags.in.logon->network = &ninfo; + + ninfo.identity_info.domain_name.string = samlogon_state->account_domain; + ninfo.identity_info.parameter_control = parameter_control; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.account_name.string = samlogon_state->account_name; + ninfo.identity_info.workstation.string = TEST_MACHINE_NAME; + + memcpy(ninfo.challenge, chall->data, 8); + + switch (break_which) { + case BREAK_NONE: + break; + case BREAK_LM: + if (lm_response && lm_response->data) { + lm_response->data[0]++; + } + break; + case BREAK_NT: + if (nt_response && nt_response->data) { + nt_response->data[0]++; + } + break; + case BREAK_BOTH: + if (lm_response && lm_response->data) { + lm_response->data[0]++; + } + if (nt_response && nt_response->data) { + nt_response->data[0]++; + } + break; + case NO_LM: + data_blob_free(lm_response); + break; + case NO_NT: + data_blob_free(nt_response); + break; + } + + if (nt_response) { + ninfo.nt.data = nt_response->data; + ninfo.nt.length = nt_response->length; + } else { + ninfo.nt.data = NULL; + ninfo.nt.length = 0; + } + + if (lm_response) { + ninfo.lm.data = lm_response->data; + ninfo.lm.length = lm_response->length; + } else { + ninfo.lm.data = NULL; + ninfo.lm.length = 0; + } + + switch (samlogon_state->function_level) { + case NDR_NETR_LOGONSAMLOGON: + ZERO_STRUCT(samlogon_state->auth2); + netlogon_creds_client_authenticator(samlogon_state->creds, &samlogon_state->auth); + + r->out.return_authenticator = NULL; + status = dcerpc_netr_LogonSamLogon_r(samlogon_state->p->binding_handle, + samlogon_state->mem_ctx, r); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + if (!r->out.return_authenticator || + !netlogon_creds_client_check(samlogon_state->creds, &r->out.return_authenticator->cred)) { + torture_comment(samlogon_state->tctx, "Credential chaining failed\n"); + } + if (!NT_STATUS_IS_OK(r->out.result)) { + if (error_string) { + *error_string = strdup(nt_errstr(r->out.result)); + } + return r->out.result; + } + + validation_level = r->in.validation_level; + + status = netlogon_creds_decrypt_samlogon_validation(samlogon_state->creds, + validation_level, + r->out.validation); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + + switch (validation_level) { + case 2: + base = &r->out.validation->sam2->base; + break; + case 3: + base = &r->out.validation->sam3->base; + break; + case 6: + base = &r->out.validation->sam6->base; + break; + } + break; + case NDR_NETR_LOGONSAMLOGONEX: + status = dcerpc_netr_LogonSamLogonEx_r(samlogon_state->p->binding_handle, + samlogon_state->mem_ctx, r_ex); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + if (!NT_STATUS_IS_OK(r_ex->out.result)) { + if (error_string) { + *error_string = strdup(nt_errstr(r_ex->out.result)); + } + return r_ex->out.result; + } + + validation_level = r_ex->in.validation_level; + + status = netlogon_creds_decrypt_samlogon_validation(samlogon_state->creds, + validation_level, + r_ex->out.validation); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + + switch (validation_level) { + case 2: + base = &r_ex->out.validation->sam2->base; + break; + case 3: + base = &r_ex->out.validation->sam3->base; + break; + case 6: + base = &r_ex->out.validation->sam6->base; + break; + } + break; + case NDR_NETR_LOGONSAMLOGONWITHFLAGS: + ZERO_STRUCT(samlogon_state->auth2); + netlogon_creds_client_authenticator(samlogon_state->creds, &samlogon_state->auth); + + r_flags->out.return_authenticator = NULL; + status = dcerpc_netr_LogonSamLogonWithFlags_r(samlogon_state->p->binding_handle, + samlogon_state->mem_ctx, r_flags); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + if (!r_flags->out.return_authenticator || + !netlogon_creds_client_check(samlogon_state->creds, &r_flags->out.return_authenticator->cred)) { + torture_comment(samlogon_state->tctx, "Credential chaining failed\n"); + } + if (!NT_STATUS_IS_OK(r_flags->out.result)) { + if (error_string) { + *error_string = strdup(nt_errstr(r_flags->out.result)); + } + return r_flags->out.result; + } + + validation_level = r_flags->in.validation_level; + + status = netlogon_creds_decrypt_samlogon_validation(samlogon_state->creds, + validation_level, + r_flags->out.validation); + if (!NT_STATUS_IS_OK(status)) { + if (error_string) { + *error_string = strdup(nt_errstr(status)); + } + return status; + } + + switch (validation_level) { + case 2: + base = &r_flags->out.validation->sam2->base; + break; + case 3: + base = &r_flags->out.validation->sam3->base; + break; + case 6: + base = &r_flags->out.validation->sam6->base; + break; + } + break; + default: + /* can't happen */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (!base) { + torture_comment(samlogon_state->tctx, "No user info returned from 'successful' SamLogon*() call!\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (user_session_key) { + memcpy(user_session_key, base->key.key, 16); + } + if (lm_key) { + memcpy(lm_key, base->LMSessKey.key, 8); + } + + return status; +} + + +/* + * Test the normal 'LM and NTLM' combination + */ + +static bool test_lm_ntlm_broken(struct samlogon_state *samlogon_state, enum ntlm_break break_which, char **error_string) +{ + bool pass = true; + bool lm_good; + NTSTATUS nt_status; + DATA_BLOB lm_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB nt_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB session_key = data_blob_talloc(samlogon_state->mem_ctx, NULL, 16); + + uint8_t lm_key[8]; + uint8_t user_session_key[16]; + uint8_t lm_hash[16]; + uint8_t nt_hash[16]; + + ZERO_STRUCT(lm_key); + ZERO_STRUCT(user_session_key); + + lm_good = SMBencrypt(samlogon_state->password, samlogon_state->chall.data, lm_response.data); + if (!lm_good) { + ZERO_STRUCT(lm_hash); + } else { + E_deshash(samlogon_state->password, lm_hash); + } + + SMBNTencrypt(samlogon_state->password, samlogon_state->chall.data, nt_response.data); + + E_md4hash(samlogon_state->password, nt_hash); + SMBsesskeygen_ntv1(nt_hash, session_key.data); + + nt_status = check_samlogon(samlogon_state, + break_which, + samlogon_state->parameter_control, + &samlogon_state->chall, + &lm_response, + &nt_response, + lm_key, + user_session_key, + error_string); + + data_blob_free(&lm_response); + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'long' passwords, the LM password is invalid */ + if (break_which == NO_NT && !lm_good) { + return true; + } + /* for modern servers, the LM password is invalid */ + if (break_which == NO_NT + && !torture_setting_bool(samlogon_state->tctx, "samba3", false)) { + return true; + } + + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH)); + } else if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, nt_status) && strchr_m(samlogon_state->account_name, '@')) { + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH) || (break_which == NO_NT)); + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (break_which == NO_NT && !lm_good) { + *error_string = strdup("LM password is 'long' (> 14 chars and therefore invalid) but login did not fail!"); + return false; + } + + /* for modern servers, the LM password is invalid */ + if (break_which == NO_NT + && !torture_setting_bool(samlogon_state->tctx, "samba3", false)) { + *error_string = strdup("LM password is OK but should have failed against a modern server"); + return false; + } + + if (!all_zero(lm_key, sizeof(lm_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + torture_comment(samlogon_state->tctx, "expected (all zeros):\n"); + pass = false; + } + + switch (break_which) { + case NO_NT: + { + uint8_t lm_key_expected[16]; + memcpy(lm_key_expected, session_key.data, 8); + memset(lm_key_expected+8, '\0', 8); + if (memcmp(lm_key_expected, user_session_key, + 16) != 0) { + *error_string = strdup("NT Session Key does not match expectations (should be first-8 session key)!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, sizeof(user_session_key)); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lm_key_expected, sizeof(lm_key_expected)); + pass = false; + } + break; + } + default: + if (memcmp(session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + *error_string = strdup("NT Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, session_key.data, session_key.length); + pass = false; + } + } + return pass; +} + +/* + * Test LM authentication, no NT response supplied + */ + +static bool test_lm(struct samlogon_state *samlogon_state, char **error_string) +{ + + return test_lm_ntlm_broken(samlogon_state, NO_NT, error_string); +} + +/* + * Test the NTLM response only, no LM. + */ + +static bool test_ntlm(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lm_ntlm_broken(samlogon_state, NO_LM, error_string); +} + +/* + * Test the NTLM response only, but in the LM field. + */ + +static bool test_ntlm_in_lm(struct samlogon_state *samlogon_state, char **error_string) +{ + bool lm_good; + bool pass = true; + NTSTATUS nt_status; + DATA_BLOB nt_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB session_key = data_blob_talloc(samlogon_state->mem_ctx, NULL, 16); + + uint8_t lm_key[8]; + uint8_t lm_hash[16]; + uint8_t user_session_key[16]; + uint8_t nt_hash[16]; + + ZERO_STRUCT(lm_key); + ZERO_STRUCT(user_session_key); + + SMBNTencrypt(samlogon_state->password, samlogon_state->chall.data, + nt_response.data); + E_md4hash(samlogon_state->password, nt_hash); + SMBsesskeygen_ntv1(nt_hash, + session_key.data); + + lm_good = E_deshash(samlogon_state->password, lm_hash); + if (!lm_good) { + ZERO_STRUCT(lm_hash); + } + nt_status = check_samlogon(samlogon_state, + BREAK_NONE, + samlogon_state->parameter_control, + &samlogon_state->chall, + &nt_response, + NULL, + lm_key, + user_session_key, + error_string); + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return false; + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (torture_setting_bool(samlogon_state->tctx, "samba4", false)) { + if (!all_zero(lm_key, sizeof(lm_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + torture_comment(samlogon_state->tctx, "expected (all zeros):\n"); + pass = false; + } + + + if (!all_zero(user_session_key, sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "NT Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, sizeof(user_session_key)); + torture_comment(samlogon_state->tctx, "expected (all zeros):\n"); + pass = false; + } + } else { + if (lm_good) { + if (memcmp(lm_hash, lm_key, + sizeof(lm_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lm_hash, 8); + pass = false; + } +#if 0 + } else { + if (memcmp(session_key.data, lm_key, + sizeof(lm_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM Key does not match expectations (first 8 session key)!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, session_key.data, 8); + pass = false; + } +#endif + } + if (lm_good && memcmp(lm_hash, user_session_key, 8) != 0) { + uint8_t lm_key_expected[16]; + memcpy(lm_key_expected, lm_hash, 8); + memset(lm_key_expected+8, '\0', 8); + if (memcmp(lm_key_expected, user_session_key, + 16) != 0) { + torture_comment(samlogon_state->tctx, "NT Session Key does not match expectations (should be first-8 LM hash)!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, sizeof(user_session_key)); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lm_key_expected, sizeof(lm_key_expected)); + pass = false; + } + } + } + return pass; +} + +/* + * Test the NTLM response only, but in the both the NT and LM fields. + */ + +static bool test_ntlm_in_both(struct samlogon_state *samlogon_state, char **error_string) +{ + bool pass = true; + bool lm_good; + NTSTATUS nt_status; + DATA_BLOB nt_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB session_key = data_blob_talloc(samlogon_state->mem_ctx, NULL, 16); + + uint8_t lm_key[8]; + uint8_t lm_hash[16]; + uint8_t user_session_key[16]; + uint8_t nt_hash[16]; + + ZERO_STRUCT(lm_key); + ZERO_STRUCT(user_session_key); + + SMBNTencrypt(samlogon_state->password, samlogon_state->chall.data, + nt_response.data); + E_md4hash(samlogon_state->password, nt_hash); + SMBsesskeygen_ntv1(nt_hash, + session_key.data); + + lm_good = E_deshash(samlogon_state->password, lm_hash); + if (!lm_good) { + ZERO_STRUCT(lm_hash); + } + + nt_status = check_samlogon(samlogon_state, + BREAK_NONE, + samlogon_state->parameter_control, + &samlogon_state->chall, + NULL, + &nt_response, + lm_key, + user_session_key, + error_string); + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return false; + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (!all_zero(lm_key, + sizeof(lm_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + torture_comment(samlogon_state->tctx, "expected (all zero)\n"); + pass = false; + } + if (memcmp(session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "NT Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, session_key.data, session_key.length); + pass = false; + } + + + return pass; +} + +/* + * Test the NTLMv2 and LMv2 responses + */ + +enum ntlmv2_domain { + UPPER_DOMAIN, + NO_DOMAIN +}; + +static bool test_lmv2_ntlmv2_broken(struct samlogon_state *samlogon_state, + enum ntlm_break break_which, + enum ntlmv2_domain ntlmv2_domain, + char **error_string) +{ + bool pass = true; + NTSTATUS nt_status; + DATA_BLOB ntlmv2_response = data_blob(NULL, 0); + DATA_BLOB lmv2_response = data_blob(NULL, 0); + DATA_BLOB lmv2_session_key = data_blob(NULL, 0); + DATA_BLOB ntlmv2_session_key = data_blob(NULL, 0); + DATA_BLOB names_blob = NTLMv2_generate_names_blob(samlogon_state->mem_ctx, TEST_MACHINE_NAME, samlogon_state->workgroup); + + uint8_t lm_session_key[8]; + uint8_t user_session_key[16]; + + ZERO_STRUCT(lm_session_key); + ZERO_STRUCT(user_session_key); + + switch (ntlmv2_domain) { + case UPPER_DOMAIN: + if (!SMBNTLMv2encrypt(samlogon_state->mem_ctx, + samlogon_state->account_name, samlogon_state->account_domain, + samlogon_state->password, &samlogon_state->chall, + &names_blob, + &lmv2_response, &ntlmv2_response, + &lmv2_session_key, &ntlmv2_session_key)) { + data_blob_free(&names_blob); + return false; + } + break; + case NO_DOMAIN: + if (!SMBNTLMv2encrypt(samlogon_state->mem_ctx, + samlogon_state->account_name, "", + samlogon_state->password, &samlogon_state->chall, + &names_blob, + &lmv2_response, &ntlmv2_response, + &lmv2_session_key, &ntlmv2_session_key)) { + data_blob_free(&names_blob); + return false; + } + break; + } + data_blob_free(&names_blob); + + nt_status = check_samlogon(samlogon_state, + break_which, + samlogon_state->parameter_control, + &samlogon_state->chall, + &lmv2_response, + &ntlmv2_response, + lm_session_key, + user_session_key, + error_string); + + data_blob_free(&lmv2_response); + data_blob_free(&ntlmv2_response); + + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return break_which == BREAK_BOTH; + } else if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, nt_status) && strchr_m(samlogon_state->account_name, '@')) { + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH) || (break_which == NO_NT)); + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + + switch (break_which) { + case NO_NT: + if (memcmp(lmv2_session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "USER (LMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lmv2_session_key.data, ntlmv2_session_key.length); + pass = false; + } + if (memcmp(lmv2_session_key.data, lm_session_key, + sizeof(lm_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM (LMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_session_key:\n"); + dump_data(1, lm_session_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lmv2_session_key.data, 8); + pass = false; + } + break; + default: + if (memcmp(ntlmv2_session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + if (memcmp(lmv2_session_key.data, user_session_key, + sizeof(user_session_key)) == 0) { + torture_comment(samlogon_state->tctx, "USER (NTLMv2) Session Key expected, got LMv2 sessesion key instead:\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlmv2_session_key.data, ntlmv2_session_key.length); + pass = false; + + } else { + torture_comment(samlogon_state->tctx, "USER (NTLMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlmv2_session_key.data, ntlmv2_session_key.length); + pass = false; + } + } + if (memcmp(ntlmv2_session_key.data, lm_session_key, + sizeof(lm_session_key)) != 0) { + if (memcmp(lmv2_session_key.data, lm_session_key, + sizeof(lm_session_key)) == 0) { + torture_comment(samlogon_state->tctx, "LM (NTLMv2) Session Key expected, got LMv2 sessesion key instead:\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, lm_session_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlmv2_session_key.data, 8); + pass = false; + } else { + torture_comment(samlogon_state->tctx, "LM (NTLMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_session_key:\n"); + dump_data(1, lm_session_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlmv2_session_key.data, 8); + pass = false; + } + } + } + + return pass; +} + +/* + * Test the NTLM and LMv2 responses + */ + +static bool test_lmv2_ntlm_broken(struct samlogon_state *samlogon_state, + enum ntlm_break break_which, + enum ntlmv2_domain ntlmv2_domain, + char **error_string) +{ + bool pass = true; + NTSTATUS nt_status; + DATA_BLOB ntlmv2_response = data_blob(NULL, 0); + DATA_BLOB lmv2_response = data_blob(NULL, 0); + DATA_BLOB lmv2_session_key = data_blob(NULL, 0); + DATA_BLOB ntlmv2_session_key = data_blob(NULL, 0); + DATA_BLOB names_blob = NTLMv2_generate_names_blob(samlogon_state->mem_ctx, samlogon_state->netbios_name, samlogon_state->workgroup); + + DATA_BLOB ntlm_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB ntlm_session_key = data_blob_talloc(samlogon_state->mem_ctx, NULL, 16); + + bool lm_good; + uint8_t lm_hash[16]; + uint8_t lm_session_key[8]; + uint8_t user_session_key[16]; + uint8_t nt_hash[16]; + + SMBNTencrypt(samlogon_state->password, samlogon_state->chall.data, + ntlm_response.data); + E_md4hash(samlogon_state->password, nt_hash); + SMBsesskeygen_ntv1(nt_hash, + ntlm_session_key.data); + + lm_good = E_deshash(samlogon_state->password, lm_hash); + if (!lm_good) { + ZERO_STRUCT(lm_hash); + } + + ZERO_STRUCT(lm_session_key); + ZERO_STRUCT(user_session_key); + + switch (ntlmv2_domain) { + case UPPER_DOMAIN: + /* TODO - test with various domain cases, and without domain */ + if (!SMBNTLMv2encrypt(samlogon_state->mem_ctx, + samlogon_state->account_name, samlogon_state->account_domain, + samlogon_state->password, &samlogon_state->chall, + &names_blob, + &lmv2_response, &ntlmv2_response, + &lmv2_session_key, &ntlmv2_session_key)) { + data_blob_free(&names_blob); + return false; + } + break; + case NO_DOMAIN: + /* TODO - test with various domain cases, and without domain */ + if (!SMBNTLMv2encrypt(samlogon_state->mem_ctx, + samlogon_state->account_name, "", + samlogon_state->password, &samlogon_state->chall, + &names_blob, + &lmv2_response, &ntlmv2_response, + &lmv2_session_key, &ntlmv2_session_key)) { + data_blob_free(&names_blob); + return false; + } + break; + } + + data_blob_free(&names_blob); + + nt_status = check_samlogon(samlogon_state, + break_which, + samlogon_state->parameter_control, + &samlogon_state->chall, + &lmv2_response, + &ntlm_response, + lm_session_key, + user_session_key, + error_string); + + data_blob_free(&lmv2_response); + data_blob_free(&ntlmv2_response); + + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH)); + } else if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, nt_status) && strchr_m(samlogon_state->account_name, '@')) { + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH)); + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + switch (break_which) { + case NO_NT: + if (memcmp(lmv2_session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "USER (LMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lmv2_session_key.data, ntlmv2_session_key.length); + pass = false; + } + if (memcmp(lmv2_session_key.data, lm_session_key, + sizeof(lm_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM (LMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_session_key:\n"); + dump_data(1, lm_session_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, lmv2_session_key.data, 8); + pass = false; + } + break; + case BREAK_LM: + if (memcmp(ntlm_session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "USER (NTLMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlm_session_key.data, ntlm_session_key.length); + pass = false; + } + if (!all_zero(lm_session_key, + sizeof(lm_session_key))) { + torture_comment(samlogon_state->tctx, "LM Session Key does not match expectations (zeros)!\n"); + torture_comment(samlogon_state->tctx, "lm_session_key:\n"); + dump_data(1, lm_session_key, 8); + pass = false; + } + break; + default: + if (memcmp(ntlm_session_key.data, user_session_key, + sizeof(user_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "USER (NTLMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, 16); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlm_session_key.data, ntlm_session_key.length); + pass = false; + } + if (memcmp(ntlm_session_key.data, lm_session_key, + sizeof(lm_session_key)) != 0) { + torture_comment(samlogon_state->tctx, "LM (NTLMv2) Session Key does not match expectations!\n"); + torture_comment(samlogon_state->tctx, "lm_session_key:\n"); + dump_data(1, lm_session_key, 8); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, ntlm_session_key.data, 8); + pass = false; + } + } + + return pass; +} + +/* + * Test the NTLMv2 and LMv2 responses + */ + +static bool test_lmv2_ntlmv2(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_NONE, UPPER_DOMAIN, error_string); +} + +#if 0 +static bool test_lmv2_ntlmv2_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_NONE, NO_DOMAIN, error_string); +} +#endif + +/* + * Test the LMv2 response only + */ + +static bool test_lmv2(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, NO_NT, UPPER_DOMAIN, error_string); +} + +static bool test_lmv2_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, NO_NT, NO_DOMAIN, error_string); +} + +/* + * Test the NTLMv2 response only + */ + +static bool test_ntlmv2(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, NO_LM, UPPER_DOMAIN, error_string); +} + +static bool test_ntlmv2_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, NO_LM, NO_DOMAIN, error_string); +} + +static bool test_lm_ntlm(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lm_ntlm_broken(samlogon_state, BREAK_NONE, error_string); +} + +static bool test_ntlm_lm_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lm_ntlm_broken(samlogon_state, BREAK_LM, error_string); +} + +static bool test_ntlm_ntlm_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lm_ntlm_broken(samlogon_state, BREAK_NT, error_string); +} + +static bool test_lm_ntlm_both_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lm_ntlm_broken(samlogon_state, BREAK_BOTH, error_string); +} +static bool test_ntlmv2_lmv2_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_LM, UPPER_DOMAIN, error_string); +} + +static bool test_ntlmv2_lmv2_broken_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_LM, NO_DOMAIN, error_string); +} + +static bool test_ntlmv2_ntlmv2_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_NT, UPPER_DOMAIN, error_string); +} + +#if 0 +static bool test_ntlmv2_ntlmv2_broken_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_NT, NO_DOMAIN, error_string); +} +#endif + +static bool test_ntlmv2_both_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_BOTH, UPPER_DOMAIN, error_string); +} + +static bool test_ntlmv2_both_broken_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlmv2_broken(samlogon_state, BREAK_BOTH, NO_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_both_broken(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_BOTH, UPPER_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_both_broken_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_BOTH, NO_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_break_ntlm(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_NT, UPPER_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_break_ntlm_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_NT, NO_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_break_lm(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_LM, UPPER_DOMAIN, error_string); +} + +static bool test_lmv2_ntlm_break_lm_no_dom(struct samlogon_state *samlogon_state, char **error_string) +{ + return test_lmv2_ntlm_broken(samlogon_state, BREAK_LM, NO_DOMAIN, error_string); +} + +/* + * Test the NTLM2 response (extra challenge in LM feild) + * + * This test is the same as the 'break LM' test, but checks that the + * server implements NTLM2 session security in the right place + * (NETLOGON is the wrong place). + */ + +static bool test_ntlm2(struct samlogon_state *samlogon_state, char **error_string) +{ + bool pass = true; + NTSTATUS nt_status; + DATA_BLOB lm_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + DATA_BLOB nt_response = data_blob_talloc(samlogon_state->mem_ctx, NULL, 24); + + uint8_t lm_key[8]; + uint8_t nt_hash[16]; + uint8_t lm_hash[16]; + uint8_t nt_key[16]; + uint8_t user_session_key[16]; + uint8_t expected_user_session_key[16]; + uint8_t session_nonce_hash[16]; + uint8_t client_chall[8]; + + gnutls_hmac_hd_t hmac_hnd; + gnutls_hash_hd_t hash_hnd; + + ZERO_STRUCT(user_session_key); + ZERO_STRUCT(lm_key); + generate_random_buffer(client_chall, 8); + + gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + gnutls_hash(hash_hnd, samlogon_state->chall.data, 8); + gnutls_hash(hash_hnd, client_chall, 8); + gnutls_hash_deinit(hash_hnd, session_nonce_hash); + + E_md4hash(samlogon_state->password, (uint8_t *)nt_hash); + E_deshash(samlogon_state->password, (uint8_t *)lm_hash); + SMBsesskeygen_ntv1((const uint8_t *)nt_hash, + nt_key); + + SMBNTencrypt(samlogon_state->password, samlogon_state->chall.data, nt_response.data); + + memcpy(lm_response.data, session_nonce_hash, 8); + memset(lm_response.data + 8, 0, 16); + + gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + nt_key, + 16); + gnutls_hmac(hmac_hnd, samlogon_state->chall.data, 8); + gnutls_hmac(hmac_hnd, client_chall, 8); + gnutls_hmac_deinit(hmac_hnd, expected_user_session_key); + + nt_status = check_samlogon(samlogon_state, + BREAK_NONE, + samlogon_state->parameter_control, + &samlogon_state->chall, + &lm_response, + &nt_response, + lm_key, + user_session_key, + error_string); + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + return false; + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (!all_zero(lm_key, sizeof(lm_key))) { + torture_comment(samlogon_state->tctx, "LM Session Key does not match expectations (zeros)!\n"); + torture_comment(samlogon_state->tctx, "lm_key:\n"); + dump_data(1, lm_key, 8); + pass = false; + } + if (memcmp(nt_key, user_session_key, 16) != 0) { + torture_comment(samlogon_state->tctx, "NT Session Key does not match expectations (should be NT Key)!\n"); + torture_comment(samlogon_state->tctx, "user_session_key:\n"); + dump_data(1, user_session_key, sizeof(user_session_key)); + torture_comment(samlogon_state->tctx, "expected:\n"); + dump_data(1, nt_key, sizeof(nt_key)); + pass = false; + } + return pass; +} + +static bool test_plaintext(struct samlogon_state *samlogon_state, enum ntlm_break break_which, char **error_string) +{ + NTSTATUS nt_status; + DATA_BLOB nt_response = data_blob(NULL, 0); + DATA_BLOB lm_response = data_blob(NULL, 0); + char *password; + char *dospw; + smb_ucs2_t *unicodepw; + size_t converted_size = 0; + uint8_t user_session_key[16]; + uint8_t lm_key[16]; + uint8_t lm_hash[16]; + DATA_BLOB chall = data_blob_talloc_zero(samlogon_state->mem_ctx, 8); + bool lm_good = E_deshash(samlogon_state->password, lm_hash); + + ZERO_STRUCT(user_session_key); + + if (!push_ucs2_talloc(samlogon_state->mem_ctx, + &unicodepw, samlogon_state->password, &converted_size)) { + DEBUG(0, ("push_ucs2_allocate failed!\n")); + exit(1); + } + + nt_response = data_blob_talloc(samlogon_state->mem_ctx, unicodepw, strlen_m(samlogon_state->password)*2); + + password = strupper_talloc(samlogon_state->mem_ctx, samlogon_state->password); + + if (!convert_string_talloc(samlogon_state->mem_ctx, + CH_UNIX, CH_DOS, + password, strlen(password)+1, + (void**)&dospw, &converted_size)) { + DEBUG(0, ("convert_string_talloc failed!\n")); + exit(1); + } + + lm_response = data_blob_talloc(samlogon_state->mem_ctx, dospw, strlen(dospw)); + + nt_status = check_samlogon(samlogon_state, + break_which, + samlogon_state->parameter_control | MSV1_0_CLEARTEXT_PASSWORD_ALLOWED, + &chall, + &lm_response, + &nt_response, + lm_key, + user_session_key, + error_string); + + if (NT_STATUS_EQUAL(NT_STATUS_WRONG_PASSWORD, nt_status)) { + /* for 'old' passwords, we allow the server to be OK or wrong password */ + if (samlogon_state->old_password) { + return true; + } + /* for 'long' passwords, the LM password is invalid */ + if (break_which == NO_NT && !lm_good) { + return true; + } + /* for modern servers, the LM password is invalid */ + if (break_which == NO_NT + && !torture_setting_bool(samlogon_state->tctx, "samba3", false)) { + return true; + } + + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH)); + } else if (NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, nt_status) && strchr_m(samlogon_state->account_name, '@')) { + return ((break_which == BREAK_NT) || (break_which == BREAK_BOTH) || (break_which == NO_NT)); + } else if (!NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status)) { + int ret; + + SAFE_FREE(*error_string); + ret = asprintf(error_string, "Expected error: %s, got %s", nt_errstr(samlogon_state->expected_error), nt_errstr(nt_status)); + if (ret == -1) { + *error_string = NULL; + } + return false; + } else if (NT_STATUS_EQUAL(samlogon_state->expected_error, nt_status) && !NT_STATUS_IS_OK(nt_status)) { + return true; + } else if (!NT_STATUS_IS_OK(nt_status)) { + return false; + } + + if (break_which == NO_NT && !lm_good) { + *error_string = strdup("LM password is 'long' (> 14 chars and therefore invalid) but login did not fail!"); + return false; + } + + /* for modern servers, the LM password is invalid */ + if (break_which == NO_NT + && !torture_setting_bool(samlogon_state->tctx, "samba3", false)) { + *error_string = strdup("LM password is OK but should have failed against a modern server"); + return false; + } + + return true; +} + +static bool test_plaintext_none_broken(struct samlogon_state *samlogon_state, + char **error_string) { + return test_plaintext(samlogon_state, BREAK_NONE, error_string); +} + +static bool test_plaintext_lm_broken(struct samlogon_state *samlogon_state, + char **error_string) { + return test_plaintext(samlogon_state, BREAK_LM, error_string); +} + +static bool test_plaintext_nt_broken(struct samlogon_state *samlogon_state, + char **error_string) { + return test_plaintext(samlogon_state, BREAK_NT, error_string); +} + +static bool test_plaintext_nt_only(struct samlogon_state *samlogon_state, + char **error_string) { + return test_plaintext(samlogon_state, NO_LM, error_string); +} + +static bool test_plaintext_lm_only(struct samlogon_state *samlogon_state, + char **error_string) { + return test_plaintext(samlogon_state, NO_NT, error_string); +} + +/* + Tests: + + - LM only + - NT and LM + - NT + - NT in LM field + - NT in both fields + - NTLMv2 + - NTLMv2 and LMv2 + - LMv2 + - plaintext tests (in challenge-response fields) + + check we get the correct session key in each case + check what values we get for the LM session key + +*/ + +static const struct ntlm_tests { + bool (*fn)(struct samlogon_state *, char **); + const char *name; + bool expect_fail; +} test_table[] = { + {test_lmv2_ntlmv2, "NTLMv2 and LMv2", false}, +#if 0 + {test_lmv2_ntlmv2_no_dom, "NTLMv2 and LMv2 (no domain)", false}, +#endif + {test_lm, "LM", false}, + {test_lm_ntlm, "LM and NTLM", false}, + {test_lm_ntlm_both_broken, "LM and NTLM, both broken", false}, + {test_ntlm, "NTLM", false}, + {test_ntlm_in_lm, "NTLM in LM", false}, + {test_ntlm_in_both, "NTLM in both", false}, + {test_ntlmv2, "NTLMv2", false}, + {test_ntlmv2_no_dom, "NTLMv2 (no domain)", false}, + {test_lmv2, "LMv2", false}, + {test_lmv2_no_dom, "LMv2 (no domain)", false}, + {test_ntlmv2_lmv2_broken, "NTLMv2 and LMv2, LMv2 broken", false}, + {test_ntlmv2_lmv2_broken_no_dom, "NTLMv2 and LMv2, LMv2 broken (no domain)", false}, + {test_ntlmv2_ntlmv2_broken, "NTLMv2 and LMv2, NTLMv2 broken", false}, +#if 0 + {test_ntlmv2_ntlmv2_broken_no_dom, "NTLMv2 and LMv2, NTLMv2 broken (no domain)", false}, +#endif + {test_ntlmv2_both_broken, "NTLMv2 and LMv2, both broken", false}, + {test_ntlmv2_both_broken_no_dom, "NTLMv2 and LMv2, both broken (no domain)", false}, + {test_ntlm_lm_broken, "NTLM and LM, LM broken", false}, + {test_ntlm_ntlm_broken, "NTLM and LM, NTLM broken", false}, + {test_ntlm2, "NTLM2 (NTLMv2 session security)", false}, + {test_lmv2_ntlm_both_broken, "LMv2 and NTLM, both broken", false}, + {test_lmv2_ntlm_both_broken_no_dom, "LMv2 and NTLM, both broken (no domain)", false}, + {test_lmv2_ntlm_break_ntlm, "LMv2 and NTLM, NTLM broken", false}, + {test_lmv2_ntlm_break_ntlm_no_dom, "LMv2 and NTLM, NTLM broken (no domain)", false}, + {test_lmv2_ntlm_break_lm, "LMv2 and NTLM, LMv2 broken", false}, + {test_lmv2_ntlm_break_lm_no_dom, "LMv2 and NTLM, LMv2 broken (no domain)", false}, + {test_plaintext_none_broken, "Plaintext", false}, + {test_plaintext_lm_broken, "Plaintext LM broken", false}, + {test_plaintext_nt_broken, "Plaintext NT broken", false}, + {test_plaintext_nt_only, "Plaintext NT only", false}, + {test_plaintext_lm_only, "Plaintext LM only", false}, + { .name = NULL, } +}; + +/* + try a netlogon SamLogon +*/ +static bool test_SamLogon(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + struct torture_context *tctx, + struct netlogon_creds_CredentialState *creds, + const char *comment, + const char *account_domain, const char *account_name, + const char *plain_pass, uint32_t parameter_control, + NTSTATUS expected_error, bool old_password, + int n_subtests) +{ + TALLOC_CTX *fn_ctx = talloc_named(mem_ctx, 0, "test_SamLogon function-level context"); + int i, v, l, f; + bool ret = true; + int validation_levels[] = {2,3,6}; + int logon_levels[] = { NetlogonNetworkInformation, NetlogonNetworkTransitiveInformation }; + int function_levels[] = { + NDR_NETR_LOGONSAMLOGON, + NDR_NETR_LOGONSAMLOGONEX, + NDR_NETR_LOGONSAMLOGONWITHFLAGS }; + struct samlogon_state samlogon_state; + + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative = 1; + uint32_t flags = 0; + + ZERO_STRUCT(logon); + + torture_comment(tctx, "Testing netr_LogonSamLogon and netr_LogonSamLogonWithFlags\n"); + + samlogon_state.comment = comment; + samlogon_state.account_name = account_name; + samlogon_state.account_domain = account_domain; + samlogon_state.password = plain_pass; + samlogon_state.workgroup = lpcfg_workgroup(tctx->lp_ctx); + samlogon_state.netbios_name = lpcfg_netbios_name(tctx->lp_ctx); + samlogon_state.p = p; + samlogon_state.creds = creds; + samlogon_state.expected_error = expected_error; + samlogon_state.chall = data_blob_talloc(fn_ctx, NULL, 8); + samlogon_state.parameter_control = parameter_control; + samlogon_state.old_password = old_password; + samlogon_state.tctx = tctx; + + generate_random_buffer(samlogon_state.chall.data, 8); + samlogon_state.r_flags.in.server_name = talloc_asprintf(fn_ctx, "\\\\%s", dcerpc_server_name(p)); + samlogon_state.r_flags.in.computer_name = TEST_MACHINE_NAME; + samlogon_state.r_flags.in.credential = &samlogon_state.auth; + samlogon_state.r_flags.in.return_authenticator = &samlogon_state.auth2; + samlogon_state.r_flags.in.flags = &flags; + samlogon_state.r_flags.in.logon = &logon; + samlogon_state.r_flags.out.validation = &validation; + samlogon_state.r_flags.out.authoritative = &authoritative; + samlogon_state.r_flags.out.flags = &flags; + + samlogon_state.r_ex.in.server_name = talloc_asprintf(fn_ctx, "\\\\%s", dcerpc_server_name(p)); + samlogon_state.r_ex.in.computer_name = TEST_MACHINE_NAME; + samlogon_state.r_ex.in.flags = &flags; + samlogon_state.r_ex.in.logon = &logon; + samlogon_state.r_ex.out.validation = &validation; + samlogon_state.r_ex.out.authoritative = &authoritative; + samlogon_state.r_ex.out.flags = &flags; + + samlogon_state.r.in.server_name = talloc_asprintf(fn_ctx, "\\\\%s", dcerpc_server_name(p)); + samlogon_state.r.in.computer_name = TEST_MACHINE_NAME; + samlogon_state.r.in.credential = &samlogon_state.auth; + samlogon_state.r.in.return_authenticator = &samlogon_state.auth2; + samlogon_state.r.in.logon = &logon; + samlogon_state.r.out.validation = &validation; + samlogon_state.r.out.authoritative = &authoritative; + + + for (f=0;f<ARRAY_SIZE(function_levels);f++) { + for (i=0; test_table[i].fn; i++) { + if (n_subtests && (i > n_subtests)) { + continue; + } + for (v=0;v<ARRAY_SIZE(validation_levels);v++) { + for (l=0;l<ARRAY_SIZE(logon_levels);l++) { + char *error_string = NULL; + TALLOC_CTX *tmp_ctx = talloc_named(fn_ctx, 0, "test_SamLogon inner loop"); + samlogon_state.mem_ctx = tmp_ctx; + samlogon_state.function_level = function_levels[f]; + samlogon_state.r.in.validation_level = validation_levels[v]; + samlogon_state.r.in.logon_level = logon_levels[l]; + samlogon_state.r_ex.in.validation_level = validation_levels[v]; + samlogon_state.r_ex.in.logon_level = logon_levels[l]; + samlogon_state.r_flags.in.validation_level = validation_levels[v]; + samlogon_state.r_flags.in.logon_level = logon_levels[l]; + if (!test_table[i].fn(&samlogon_state, &error_string)) { + torture_comment(tctx, "Testing '%s' [%s]\\[%s] '%s' at validation level %d, logon level %d, function %d: \n", + samlogon_state.comment, + samlogon_state.account_domain, + samlogon_state.account_name, + test_table[i].name, validation_levels[v], + logon_levels[l], function_levels[f]); + + if (test_table[i].expect_fail) { + torture_comment(tctx, " failed (expected, test incomplete): %s\n", error_string); + } else { + torture_comment(tctx, " failed: %s\n", error_string); + ret = false; + } + SAFE_FREE(error_string); + } + talloc_free(tmp_ctx); + } + } + } + } + talloc_free(fn_ctx); + return ret; +} + +/* + test an ADS style interactive domain logon +*/ +bool test_InteractiveLogon(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + struct torture_context *tctx, + struct netlogon_creds_CredentialState *creds, + const char *comment, + const char *workstation_name, + const char *account_domain, const char *account_name, + const char *plain_pass, uint32_t parameter_control, + NTSTATUS expected_error) +{ + NTSTATUS status; + TALLOC_CTX *fn_ctx = talloc_named(mem_ctx, 0, "test_InteractiveLogon function-level context"); + bool ret = true; + struct netr_LogonSamLogonWithFlags r; + struct netr_Authenticator a, ra; + struct netr_PasswordInfo pinfo; + uint32_t flags = 0; + + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative = 1; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(a); + ZERO_STRUCT(r); + ZERO_STRUCT(ra); + + ZERO_STRUCT(logon); + ZERO_STRUCT(validation); + + netlogon_creds_client_authenticator(creds, &a); + + logon.password = &pinfo; + + r.in.server_name = talloc_asprintf(fn_ctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &a; + r.in.return_authenticator = &ra; + r.in.logon_level = NetlogonInteractiveTransitiveInformation; + r.in.logon = &logon; + r.in.validation_level = 6; + r.in.flags = &flags; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + r.out.flags = &flags; + + pinfo.identity_info.domain_name.string = account_domain; + pinfo.identity_info.parameter_control = parameter_control; + pinfo.identity_info.logon_id = 0; + pinfo.identity_info.account_name.string = account_name; + pinfo.identity_info.workstation.string = workstation_name; + + if (!E_deshash(plain_pass, pinfo.lmpassword.hash)) { + ZERO_STRUCT(pinfo.lmpassword.hash); + } + E_md4hash(plain_pass, pinfo.ntpassword.hash); + + if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + netlogon_creds_arcfour_crypt(creds, pinfo.lmpassword.hash, 16); + netlogon_creds_arcfour_crypt(creds, pinfo.ntpassword.hash, 16); + } else { + netlogon_creds_des_encrypt(creds, &pinfo.lmpassword); + netlogon_creds_des_encrypt(creds, &pinfo.ntpassword); + } + + torture_comment(tctx, "Testing netr_LogonSamLogonWithFlags '%s' (Interactive Logon)\n", comment); + + status = dcerpc_netr_LogonSamLogonWithFlags_r(b, fn_ctx, &r); + torture_assert_ntstatus_ok_goto(tctx, + status, + ret, failed, + talloc_asprintf(tctx, "%s: netr_LogonSamLogonWithFlags - %s\n", + __location__, nt_errstr(status))); + + if (!r.out.return_authenticator) { + talloc_free(fn_ctx); + torture_fail(tctx, "no authenticator returned"); + } + + torture_assert_goto(tctx, + netlogon_creds_client_check(creds, &r.out.return_authenticator->cred), + ret, failed, + "Credential chaining failed\n"); + + torture_assert_ntstatus_equal(tctx, r.out.result, expected_error, + talloc_asprintf(tctx, "[%s]\\[%s] netr_LogonSamLogonWithFlags - expected %s got %s\n", + account_domain, account_name, nt_errstr(expected_error), nt_errstr(r.out.result))); + + ret = true; + failed: + talloc_free(fn_ctx); + + return ret; +} + +/* This sets and resets the "minPwdAge" (in order to allow immediate user + * password changes). The behaviour is controlled by the "set" boolean. */ +static bool handle_minPwdAge(struct torture_context *torture, + TALLOC_CTX *mem_ctx, bool set) +{ + struct dcerpc_pipe *p; + struct policy_handle connect_handle, domain_handle; + struct samr_Connect c_r; + struct samr_LookupDomain ld_r; + struct samr_OpenDomain od_r; + struct samr_QueryDomainInfo qdi_r; + struct samr_SetDomainInfo sdi_r; + struct samr_Close cl_r; + struct lsa_String domName; + struct dom_sid *domSid = NULL; + union samr_DomainInfo *domInfo = NULL; + static int64_t old_minPwdAge = 0; + NTSTATUS status; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + c_r.in.system_name = 0; + c_r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + c_r.out.connect_handle = &connect_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_Connect_r(p->binding_handle, mem_ctx, &c_r), + "Connect failed"); + torture_assert_ntstatus_ok(torture, c_r.out.result, "Connect failed"); + + ld_r.in.connect_handle = &connect_handle; + ld_r.in.domain_name = &domName; + ld_r.in.domain_name->string = lpcfg_workgroup(torture->lp_ctx); + ld_r.out.sid = &domSid; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_LookupDomain_r(p->binding_handle, mem_ctx, &ld_r), + "LookupDomain failed"); + torture_assert_ntstatus_ok(torture, ld_r.out.result, + "LookupDomain failed"); + + od_r.in.connect_handle = &connect_handle; + od_r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + od_r.in.sid = *ld_r.out.sid; + od_r.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_OpenDomain_r(p->binding_handle, mem_ctx, &od_r), + "OpenDomain failed"); + torture_assert_ntstatus_ok(torture, od_r.out.result, + "OpenDomain failed"); + + qdi_r.in.domain_handle = &domain_handle; + qdi_r.in.level = DomainPasswordInformation; + qdi_r.out.info = &domInfo; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_QueryDomainInfo_r(p->binding_handle, mem_ctx, &qdi_r), + "QueryDomainInfo failed"); + torture_assert_ntstatus_ok(torture, qdi_r.out.result, + "QueryDomainInfo failed"); + + if (set) { + old_minPwdAge = domInfo->info1.min_password_age; + domInfo->info1.min_password_age = 0; + } else { + domInfo->info1.min_password_age = old_minPwdAge; + } + + sdi_r.in.domain_handle = &domain_handle; + sdi_r.in.level = DomainPasswordInformation; + sdi_r.in.info = domInfo; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_SetDomainInfo_r(p->binding_handle, mem_ctx, &sdi_r), + "SetDomainInfo failed"); + torture_assert_ntstatus_ok(torture, sdi_r.out.result, + "SetDomainInfo failed"); + + cl_r.in.handle = &connect_handle; + cl_r.out.handle = &connect_handle; + + torture_assert_ntstatus_ok(torture, + dcerpc_samr_Close_r(p->binding_handle, mem_ctx, &cl_r), + "Close failed"); + torture_assert_ntstatus_ok(torture, cl_r.out.result, "Close failed"); + + return true; +} + +bool torture_rpc_samlogon(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + struct dcerpc_binding *b; + struct cli_credentials *machine_credentials; + TALLOC_CTX *mem_ctx = talloc_init("torture_rpc_netlogon"); + bool ret = true; + struct test_join *join_ctx = NULL; + struct test_join *user_ctx = NULL, *user_ctx_wrong_wks = NULL, *user_ctx_wrong_time = NULL; + const char *old_user_password, *user_password_wrong_wks, *user_password_wrong_time; + char *user_password; + const char *userdomain; + struct samr_SetUserInfo s; + union samr_UserInfo u; + int i; + int ci; + + unsigned int credential_flags[] = { + NETLOGON_NEG_AUTH2_FLAGS, + NETLOGON_NEG_ARCFOUR, + NETLOGON_NEG_ARCFOUR | NETLOGON_NEG_128BIT, + NETLOGON_NEG_AUTH2_ADS_FLAGS, + 0 /* yes, this is a valid flag, causes the use of DES */ + }; + + struct netlogon_creds_CredentialState *creds; + struct dcerpc_pipe *tmp_p = NULL; + + torture_assert(torture, handle_minPwdAge(torture, mem_ctx, true), + "handle_minPwdAge error!"); + + /* We only need to join as a workstation here, and in future, + * if we wish to test against trusted domains, we must be a + * workstation here */ + join_ctx = torture_join_domain(torture, TEST_MACHINE_NAME, ACB_WSTRUST, + &machine_credentials); + torture_assert(torture, join_ctx, "Failed to join as Workstation\n"); + + userdomain = torture_setting_string(torture, "userdomain", lpcfg_workgroup(torture->lp_ctx)); + + user_ctx = torture_create_testuser(torture, + TEST_USER_NAME, + userdomain, + ACB_NORMAL, + &old_user_password); + torture_assert(torture, user_ctx, "Failed to create a test user\n"); + + user_password = talloc_strdup(torture, old_user_password); + torture_assert(torture, user_password != NULL, "Failed to copy old_user_password\n"); + + tmp_p = torture_join_samr_pipe(user_ctx); + torture_assert(torture, tmp_p, "torture_join_samr_pipe failed\n"); + test_ChangePasswordUser3(tmp_p, torture, + TEST_USER_NAME, 16 /* > 14 */, &user_password, + NULL, 0, false); + + user_ctx_wrong_wks = torture_create_testuser(torture, + TEST_USER_NAME_WRONG_WKS, + userdomain, + ACB_NORMAL, + &user_password_wrong_wks); + torture_assert(torture, user_ctx_wrong_wks, + "Failed to create a test user (wrong workstation test)\n"); + + ZERO_STRUCT(u); + s.in.user_handle = torture_join_samr_user_policy(user_ctx_wrong_wks); + s.in.info = &u; + s.in.level = 21; + + u.info21.fields_present = SAMR_FIELD_WORKSTATIONS; + u.info21.workstations.string = "not" TEST_MACHINE_NAME; + + tmp_p = torture_join_samr_pipe(user_ctx_wrong_wks); + status = dcerpc_samr_SetUserInfo_r(tmp_p->binding_handle, mem_ctx, &s); + torture_assert_ntstatus_ok_goto(torture, status, ret, failed, + talloc_asprintf(torture, "SetUserInfo (list of workstations) failed - %s\n", nt_errstr(status))); + torture_assert_ntstatus_ok_goto(torture, s.out.result, ret, failed, + talloc_asprintf(torture, "SetUserInfo (list of workstations) failed - %s\n", nt_errstr(s.out.result))); + + user_ctx_wrong_time + = torture_create_testuser(torture, TEST_USER_NAME_WRONG_TIME, + userdomain, + ACB_NORMAL, + &user_password_wrong_time); + torture_assert(torture, user_ctx_wrong_time, + "Failed to create a test user (wrong workstation test)\n"); + + ZERO_STRUCT(u); + s.in.user_handle = torture_join_samr_user_policy(user_ctx_wrong_time); + s.in.info = &u; + s.in.level = 21; + + u.info21.fields_present = SAMR_FIELD_WORKSTATIONS | SAMR_FIELD_LOGON_HOURS; + u.info21.workstations.string = TEST_MACHINE_NAME; + u.info21.logon_hours.units_per_week = 168; + u.info21.logon_hours.bits = talloc_zero_array(mem_ctx, uint8_t, 168); + + tmp_p = torture_join_samr_pipe(user_ctx_wrong_time); + status = dcerpc_samr_SetUserInfo_r(tmp_p->binding_handle, mem_ctx, &s); + torture_assert_ntstatus_ok_goto(torture, status, ret, failed, + talloc_asprintf(torture, "SetUserInfo (logon times and list of workstations) failed - %s\n", nt_errstr(status))); + torture_assert_ntstatus_ok_goto(torture, s.out.result, ret, failed, + talloc_asprintf(torture, "SetUserInfo (list of workstations) failed - %s\n", nt_errstr(s.out.result))); + + + torture_assert_ntstatus_ok_goto(torture, + torture_rpc_binding(torture, &b), + ret, + failed, + "Obtaining binding"); + + /* We have to use schannel, otherwise the SamLogonEx fails + * with INTERNAL_ERROR */ + + status = dcerpc_binding_set_flags(b, + DCERPC_SCHANNEL | + DCERPC_SIGN | DCERPC_SEAL | + DCERPC_SCHANNEL_128, + DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(torture, status, "set flags"); + + status = dcerpc_pipe_connect_b(mem_ctx, &p, b, + &ndr_table_netlogon, + machine_credentials, torture->ev, torture->lp_ctx); + + torture_assert_ntstatus_ok_goto(torture, status, ret, failed, + talloc_asprintf(torture, "RPC pipe connect as domain member failed: %s\n", nt_errstr(status))); + + torture_assert_not_null_goto(torture, + creds = cli_credentials_get_netlogon_creds(machine_credentials), + ret, + failed, + "obtaining credentails"); + + { + + struct { + const char *comment; + const char *domain; + const char *username; + const char *password; + bool network_login; + NTSTATUS expected_interactive_error; + NTSTATUS expected_network_error; + uint32_t parameter_control; + bool old_password; /* Allow an old password to be accepted or rejected without error, as well as session key bugs */ + } usercreds[] = { + { + .comment = "domain\\user", + .domain = cli_credentials_get_domain( + samba_cmdline_get_creds()), + .username = cli_credentials_get_username( + samba_cmdline_get_creds()), + .password = cli_credentials_get_password( + samba_cmdline_get_creds()), + .network_login = true, + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "realm\\user", + .domain = cli_credentials_get_realm( + samba_cmdline_get_creds()), + .username = cli_credentials_get_username( + samba_cmdline_get_creds()), + .password = cli_credentials_get_password( + samba_cmdline_get_creds()), + .network_login = true, + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "user@domain", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + cli_credentials_get_username( + samba_cmdline_get_creds()), + cli_credentials_get_domain( + samba_cmdline_get_creds()) + ), + .password = cli_credentials_get_password( + samba_cmdline_get_creds()), + .network_login = false, /* works for some things, but not NTLMv2. Odd */ + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "user@realm", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + cli_credentials_get_username( + samba_cmdline_get_creds()), + cli_credentials_get_realm( + samba_cmdline_get_creds()) + ), + .password = cli_credentials_get_password( + samba_cmdline_get_creds()), + .network_login = true, + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "machine domain\\user", + .domain = cli_credentials_get_domain(machine_credentials), + .username = cli_credentials_get_username(machine_credentials), + .password = cli_credentials_get_password(machine_credentials), + .network_login = true, + .expected_interactive_error = NT_STATUS_NO_SUCH_USER, + .parameter_control = MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT + }, + { + .comment = "machine domain\\user", + .domain = cli_credentials_get_domain(machine_credentials), + .username = cli_credentials_get_username(machine_credentials), + .password = cli_credentials_get_password(machine_credentials), + .network_login = true, + .expected_interactive_error = NT_STATUS_NO_SUCH_USER, + .expected_network_error = NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT, + .parameter_control = 0, + }, + { + .comment = "machine realm\\user", + .domain = cli_credentials_get_realm(machine_credentials), + .username = cli_credentials_get_username(machine_credentials), + .password = cli_credentials_get_password(machine_credentials), + .network_login = true, + .expected_interactive_error = NT_STATUS_NO_SUCH_USER, + .parameter_control = MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT + }, + { + .comment = "machine user@domain", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + cli_credentials_get_username(machine_credentials), + cli_credentials_get_domain(machine_credentials) + ), + .password = cli_credentials_get_password(machine_credentials), + .network_login = false, /* works for some things, but not NTLMv2. Odd */ + .expected_interactive_error = NT_STATUS_NO_SUCH_USER, + .parameter_control = MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT + }, + { + .comment = "machine user@realm", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + cli_credentials_get_username(machine_credentials), + cli_credentials_get_realm(machine_credentials) + ), + .password = cli_credentials_get_password(machine_credentials), + .network_login = true, + .expected_interactive_error = NT_STATUS_NO_SUCH_USER, + .parameter_control = MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT + }, + { + .comment = "test user (long pw): domain\\user", + .domain = userdomain, + .username = TEST_USER_NAME, + .password = user_password, + .network_login = true, + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "test user (long pw): user@realm", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + TEST_USER_NAME, + lpcfg_realm(torture->lp_ctx)), + .password = user_password, + .network_login = true, + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + { + .comment = "test user (long pw): user@domain", + .domain = NULL, + .username = talloc_asprintf(mem_ctx, + "%s@%s", + TEST_USER_NAME, + userdomain), + .password = user_password, + .network_login = false, /* works for some things, but not NTLMv2. Odd */ + .expected_interactive_error = NT_STATUS_OK, + .expected_network_error = NT_STATUS_OK, + .parameter_control = 0, + }, + /* Oddball, can we use the old password ? */ + { + .comment = "test user: user\\domain OLD PASSWORD", + .domain = userdomain, + .username = TEST_USER_NAME, + .password = old_user_password, + .network_login = true, + .expected_interactive_error = NT_STATUS_WRONG_PASSWORD, + .expected_network_error = NT_STATUS_OK, + .old_password = true + }, + { + .comment = "test user (wrong workstation): domain\\user", + .domain = userdomain, + .username = TEST_USER_NAME_WRONG_WKS, + .password = user_password_wrong_wks, + .network_login = true, + .expected_interactive_error = NT_STATUS_INVALID_WORKSTATION, + .expected_network_error = NT_STATUS_INVALID_WORKSTATION, + .parameter_control = 0, + } + }; + + /* Try all the tests for different username forms */ + for (ci = 0; ci < ARRAY_SIZE(usercreds); ci++) { + + torture_assert_goto(torture, + test_InteractiveLogon(p, mem_ctx, torture, creds, + usercreds[ci].comment, + TEST_MACHINE_NAME, + usercreds[ci].domain, + usercreds[ci].username, + usercreds[ci].password, + usercreds[ci].parameter_control, + usercreds[ci].expected_interactive_error), + ret, + failed, + talloc_asprintf(mem_ctx, "InteractiveLogon: %s", + usercreds[ci].comment)); + + if (usercreds[ci].network_login) { + torture_assert_goto(torture, + test_SamLogon(p, mem_ctx, torture, creds, + usercreds[ci].comment, + usercreds[ci].domain, + usercreds[ci].username, + usercreds[ci].password, + usercreds[ci].parameter_control, + usercreds[ci].expected_network_error, + usercreds[ci].old_password, + 0), + ret, + failed, + talloc_asprintf(mem_ctx, "SamLogon: %s", + usercreds[ci].comment)); + } + } + + /* Using the first username form, try the different + * credentials flag setups, on only one of the tests (checks + * session key encryption) */ + + for (i=0; i < ARRAY_SIZE(credential_flags); i++) { + /* TODO: Somehow we lost setting up the different credential flags here! */ + + torture_comment(torture, + "Testing with flags: 0x%08x\n", + credential_flags[i]); + + torture_assert_goto(torture, + test_InteractiveLogon(p, mem_ctx, torture, creds, + usercreds[0].comment, + TEST_MACHINE_NAME, + usercreds[0].domain, + usercreds[0].username, + usercreds[0].password, + usercreds[0].parameter_control, + usercreds[0].expected_interactive_error), + ret, + failed, + talloc_asprintf(mem_ctx, + "Testing InteractiveLogon with flags: 0x%08x\n", + credential_flags[i])); + + if (usercreds[0].network_login) { + torture_assert_goto(torture, + test_SamLogon(p, mem_ctx, torture, creds, + usercreds[0].comment, + usercreds[0].domain, + usercreds[0].username, + usercreds[0].password, + usercreds[0].parameter_control, + usercreds[0].expected_network_error, + usercreds[0].old_password, + 1), + ret, + failed, + talloc_asprintf(mem_ctx, + "Testing SamLogon with flags: 0x%08x\n", + credential_flags[i])); + } + } + + } +failed: + torture_assert(torture, handle_minPwdAge(torture, mem_ctx, false), + "handle_minPwdAge error!"); + + talloc_free(mem_ctx); + + torture_leave_domain(torture, join_ctx); + torture_leave_domain(torture, user_ctx); + torture_leave_domain(torture, user_ctx_wrong_wks); + torture_leave_domain(torture, user_ctx_wrong_time); + return ret; +} diff --git a/source4/torture/rpc/samr.c b/source4/torture/rpc/samr.c new file mode 100644 index 0000000..0b1880e --- /dev/null +++ b/source4/torture/rpc/samr.c @@ -0,0 +1,9468 @@ +/* + Unix SMB/CIFS implementation. + test suite for samr rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003 + Copyright (C) Jelmer Vernooij 2005-2007 + Copyright (C) Guenther Deschner 2008-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 "torture/torture.h" +#include <tevent.h> +#include "system/time.h" +#include "system/network.h" +#include "librpc/gen_ndr/lsa.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "lib/crypto/crypto.h" +#include "libcli/auth/libcli_auth.h" +#include "libcli/security/security.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_proto.h" +#include "../libcli/auth/schannel.h" +#include "torture/util.h" +#include "source4/librpc/rpc/dcerpc.h" +#include "librpc/rpc/dcerpc_samr.h" +#include "source3/rpc_client/init_samr.h" +#include "lib/crypto/gnutls_helpers.h" + +#undef strcasecmp + +#define TEST_ACCOUNT_NAME "samrtorturetest" +#define TEST_ACCOUNT_NAME_PWD "samrpwdlastset" +#define TEST_ALIASNAME "samrtorturetestalias" +#define TEST_GROUPNAME "samrtorturetestgroup" +#define TEST_MACHINENAME "samrtestmach$" +#define TEST_DOMAINNAME "samrtestdom$" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +enum torture_samr_choice { + TORTURE_SAMR_PASSWORDS, + TORTURE_SAMR_PASSWORDS_PWDLASTSET, + TORTURE_SAMR_PASSWORDS_BADPWDCOUNT, + TORTURE_SAMR_PASSWORDS_LOCKOUT, + TORTURE_SAMR_USER_ATTRIBUTES, + TORTURE_SAMR_USER_PRIVILEGES, + TORTURE_SAMR_OTHER, + TORTURE_SAMR_MANY_ACCOUNTS, + TORTURE_SAMR_MANY_GROUPS, + TORTURE_SAMR_MANY_ALIASES +}; + +struct torture_samr_context { + struct policy_handle handle; + struct cli_credentials *machine_credentials; + enum torture_samr_choice choice; + uint32_t num_objects_large_dc; +}; + +static bool test_QueryUserInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle); + +static bool test_QueryUserInfo2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle); + +static bool test_QueryAliasInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle); + +static bool test_ChangePassword(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *acct_name, + struct policy_handle *domain_handle, char **password); + +static void init_lsa_String(struct lsa_String *string, const char *s) +{ + string->string = s; +} + +static void init_lsa_StringLarge(struct lsa_StringLarge *string, const char *s) +{ + string->string = s; +} + +static void init_lsa_BinaryString(struct lsa_BinaryString *string, const char *s, uint32_t length) +{ + string->length = length; + string->size = length; + string->array = (uint16_t *)discard_const(s); +} + +bool test_samr_handle_Close(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_Close r; + + r.in.handle = handle; + r.out.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Close_r(b, tctx, &r), + "Close failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "Close failed"); + + return true; +} + +static bool test_Shutdown(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_Shutdown r; + + if (!torture_setting_bool(tctx, "dangerous", false)) { + torture_skip(tctx, "samr_Shutdown disabled - enable dangerous tests to use\n"); + return true; + } + + r.in.connect_handle = handle; + + torture_comment(tctx, "Testing samr_Shutdown\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Shutdown_r(b, tctx, &r), + "Shutdown failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "Shutdown failed"); + + return true; +} + +static bool test_SetDsrmPassword(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_SetDsrmPassword r; + struct lsa_String string; + struct samr_Password hash; + + if (!torture_setting_bool(tctx, "dangerous", false)) { + torture_skip(tctx, "samr_SetDsrmPassword disabled - enable dangerous tests to use"); + } + + E_md4hash("TeSTDSRM123", hash.hash); + + init_lsa_String(&string, "Administrator"); + + r.in.name = &string; + r.in.unknown = 0; + r.in.hash = &hash; + + torture_comment(tctx, "Testing samr_SetDsrmPassword\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDsrmPassword_r(b, tctx, &r), + "SetDsrmPassword failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_SUPPORTED, "SetDsrmPassword failed"); + + return true; +} + + +static bool test_QuerySecurity(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QuerySecurity r; + struct samr_SetSecurity s; + struct sec_desc_buf *sdbuf = NULL; + + r.in.handle = handle; + r.in.sec_info = 7; + r.out.sdbuf = &sdbuf; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QuerySecurity_r(b, tctx, &r), + "QuerySecurity failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "QuerySecurity failed"); + + torture_assert(tctx, sdbuf != NULL, "sdbuf is NULL"); + + s.in.handle = handle; + s.in.sec_info = 7; + s.in.sdbuf = sdbuf; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "skipping SetSecurity test against Samba4\n"); + } + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetSecurity_r(b, tctx, &s), + "SetSecurity failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "SetSecurity failed"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QuerySecurity_r(b, tctx, &r), + "QuerySecurity failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "QuerySecurity failed"); + + return true; +} + + +static bool test_SetUserInfo(struct dcerpc_binding_handle *b, struct torture_context *tctx, + struct policy_handle *handle, uint32_t base_acct_flags, + const char *base_account_name) +{ + struct samr_SetUserInfo s; + struct samr_SetUserInfo2 s2; + struct samr_QueryUserInfo q; + struct samr_QueryUserInfo q0; + union samr_UserInfo u; + union samr_UserInfo *info; + bool ret = true; + const char *test_account_name; + + uint32_t user_extra_flags = 0; + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (base_acct_flags == ACB_NORMAL) { + /* When created, accounts are expired by default */ + user_extra_flags = ACB_PW_EXPIRED; + } + } + + s.in.user_handle = handle; + s.in.info = &u; + + s2.in.user_handle = handle; + s2.in.info = &u; + + q.in.user_handle = handle; + q.out.info = &info; + q0 = q; + +#define TESTCALL(call, r) \ + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ ##call## _r(b, tctx, &r),\ + #call " failed"); \ + if (!NT_STATUS_IS_OK(r.out.result)) { \ + torture_result(tctx, TORTURE_FAIL, #call " level %u failed - %s (%s)\n", \ + r.in.level, nt_errstr(r.out.result), __location__); \ + ret = false; \ + break; \ + } + +#define STRING_EQUAL(s1, s2, field) \ + torture_assert_str_equal(tctx, s1, s2, "Failed to set " #field) + +#define MEM_EQUAL(s1, s2, length, field) \ + torture_assert_mem_equal(tctx, s1, s2, length, "Failed to set " #field) + +#define INT_EQUAL(i1, i2, field) \ + torture_assert_int_equal(tctx, i1, i2, "Failed to set " #field) + +#define TEST_USERINFO_STRING(lvl1, field1, lvl2, field2, value, fpval) do { \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTCALL(QueryUserInfo, q) \ + s.in.level = lvl1; \ + s2.in.level = lvl1; \ + u = *info; \ + if (lvl1 == 21) { \ + ZERO_STRUCT(u.info21); \ + u.info21.fields_present = fpval; \ + } \ + init_lsa_String(&u.info ## lvl1.field1, value); \ + TESTCALL(SetUserInfo, s) \ + TESTCALL(SetUserInfo2, s2) \ + init_lsa_String(&u.info ## lvl1.field1, ""); \ + TESTCALL(QueryUserInfo, q); \ + u = *info; \ + STRING_EQUAL(u.info ## lvl1.field1.string, value, field1); \ + q.in.level = lvl2; \ + TESTCALL(QueryUserInfo, q) \ + u = *info; \ + STRING_EQUAL(u.info ## lvl2.field2.string, value, field2); \ + } while (0) + +#define TEST_USERINFO_BINARYSTRING(lvl1, field1, lvl2, field2, value, fpval) do { \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTCALL(QueryUserInfo, q) \ + s.in.level = lvl1; \ + s2.in.level = lvl1; \ + u = *info; \ + if (lvl1 == 21) { \ + ZERO_STRUCT(u.info21); \ + u.info21.fields_present = fpval; \ + } \ + init_lsa_BinaryString(&u.info ## lvl1.field1, value, strlen(value)); \ + TESTCALL(SetUserInfo, s) \ + TESTCALL(SetUserInfo2, s2) \ + init_lsa_BinaryString(&u.info ## lvl1.field1, "", 1); \ + TESTCALL(QueryUserInfo, q); \ + u = *info; \ + MEM_EQUAL(u.info ## lvl1.field1.array, value, strlen(value), field1); \ + q.in.level = lvl2; \ + TESTCALL(QueryUserInfo, q) \ + u = *info; \ + MEM_EQUAL(u.info ## lvl2.field2.array, value, strlen(value), field2); \ + } while (0) + +#define TEST_USERINFO_INT_EXP(lvl1, field1, lvl2, field2, value, exp_value, fpval) do { \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTCALL(QueryUserInfo, q) \ + s.in.level = lvl1; \ + s2.in.level = lvl1; \ + u = *info; \ + if (lvl1 == 21) { \ + uint8_t *bits = u.info21.logon_hours.bits; \ + ZERO_STRUCT(u.info21); \ + if (fpval == SAMR_FIELD_LOGON_HOURS) { \ + u.info21.logon_hours.units_per_week = 168; \ + u.info21.logon_hours.bits = bits; \ + } \ + u.info21.fields_present = fpval; \ + } \ + u.info ## lvl1.field1 = value; \ + TESTCALL(SetUserInfo, s) \ + TESTCALL(SetUserInfo2, s2) \ + u.info ## lvl1.field1 = 0; \ + TESTCALL(QueryUserInfo, q); \ + u = *info; \ + INT_EQUAL(u.info ## lvl1.field1, exp_value, field1); \ + q.in.level = lvl2; \ + TESTCALL(QueryUserInfo, q) \ + u = *info; \ + INT_EQUAL(u.info ## lvl2.field2, exp_value, field1); \ + } while (0) + +#define TEST_USERINFO_INT(lvl1, field1, lvl2, field2, value, fpval) do { \ + TEST_USERINFO_INT_EXP(lvl1, field1, lvl2, field2, value, value, fpval); \ + } while (0) + + q0.in.level = 12; + do { TESTCALL(QueryUserInfo, q0) } while (0); + + TEST_USERINFO_STRING(2, comment, 1, comment, "xx2-1 comment", 0); + TEST_USERINFO_STRING(2, comment, 21, comment, "xx2-21 comment", 0); + TEST_USERINFO_STRING(21, comment, 21, comment, "xx21-21 comment", + SAMR_FIELD_COMMENT); + + test_account_name = talloc_asprintf(tctx, "%sxx7-1", base_account_name); + TEST_USERINFO_STRING(7, account_name, 1, account_name, test_account_name, 0); + test_account_name = talloc_asprintf(tctx, "%sxx7-3", base_account_name); + TEST_USERINFO_STRING(7, account_name, 3, account_name, test_account_name, 0); + test_account_name = talloc_asprintf(tctx, "%sxx7-5", base_account_name); + TEST_USERINFO_STRING(7, account_name, 5, account_name, test_account_name, 0); + test_account_name = talloc_asprintf(tctx, "%sxx7-6", base_account_name); + TEST_USERINFO_STRING(7, account_name, 6, account_name, test_account_name, 0); + test_account_name = talloc_asprintf(tctx, "%sxx7-7", base_account_name); + TEST_USERINFO_STRING(7, account_name, 7, account_name, test_account_name, 0); + test_account_name = talloc_asprintf(tctx, "%sxx7-21", base_account_name); + TEST_USERINFO_STRING(7, account_name, 21, account_name, test_account_name, 0); + test_account_name = base_account_name; + TEST_USERINFO_STRING(21, account_name, 21, account_name, test_account_name, + SAMR_FIELD_ACCOUNT_NAME); + + TEST_USERINFO_STRING(6, full_name, 1, full_name, "xx6-1 full_name", 0); + TEST_USERINFO_STRING(6, full_name, 3, full_name, "xx6-3 full_name", 0); + TEST_USERINFO_STRING(6, full_name, 5, full_name, "xx6-5 full_name", 0); + TEST_USERINFO_STRING(6, full_name, 6, full_name, "xx6-6 full_name", 0); + TEST_USERINFO_STRING(6, full_name, 8, full_name, "xx6-8 full_name", 0); + TEST_USERINFO_STRING(6, full_name, 21, full_name, "xx6-21 full_name", 0); + TEST_USERINFO_STRING(8, full_name, 21, full_name, "xx8-21 full_name", 0); + TEST_USERINFO_STRING(21, full_name, 21, full_name, "xx21-21 full_name", + SAMR_FIELD_FULL_NAME); + + TEST_USERINFO_STRING(6, full_name, 1, full_name, "", 0); + TEST_USERINFO_STRING(6, full_name, 3, full_name, "", 0); + TEST_USERINFO_STRING(6, full_name, 5, full_name, "", 0); + TEST_USERINFO_STRING(6, full_name, 6, full_name, "", 0); + TEST_USERINFO_STRING(6, full_name, 8, full_name, "", 0); + TEST_USERINFO_STRING(6, full_name, 21, full_name, "", 0); + TEST_USERINFO_STRING(8, full_name, 21, full_name, "", 0); + TEST_USERINFO_STRING(21, full_name, 21, full_name, "", + SAMR_FIELD_FULL_NAME); + + TEST_USERINFO_STRING(11, logon_script, 3, logon_script, "xx11-3 logon_script", 0); + TEST_USERINFO_STRING(11, logon_script, 5, logon_script, "xx11-5 logon_script", 0); + TEST_USERINFO_STRING(11, logon_script, 21, logon_script, "xx11-21 logon_script", 0); + TEST_USERINFO_STRING(21, logon_script, 21, logon_script, "xx21-21 logon_script", + SAMR_FIELD_LOGON_SCRIPT); + + TEST_USERINFO_STRING(12, profile_path, 3, profile_path, "xx12-3 profile_path", 0); + TEST_USERINFO_STRING(12, profile_path, 5, profile_path, "xx12-5 profile_path", 0); + TEST_USERINFO_STRING(12, profile_path, 21, profile_path, "xx12-21 profile_path", 0); + TEST_USERINFO_STRING(21, profile_path, 21, profile_path, "xx21-21 profile_path", + SAMR_FIELD_PROFILE_PATH); + + TEST_USERINFO_STRING(10, home_directory, 3, home_directory, "xx10-3 home_directory", 0); + TEST_USERINFO_STRING(10, home_directory, 5, home_directory, "xx10-5 home_directory", 0); + TEST_USERINFO_STRING(10, home_directory, 21, home_directory, "xx10-21 home_directory", 0); + TEST_USERINFO_STRING(21, home_directory, 21, home_directory, "xx21-21 home_directory", + SAMR_FIELD_HOME_DIRECTORY); + TEST_USERINFO_STRING(21, home_directory, 10, home_directory, "xx21-10 home_directory", + SAMR_FIELD_HOME_DIRECTORY); + + TEST_USERINFO_STRING(10, home_drive, 3, home_drive, "xx10-3 home_drive", 0); + TEST_USERINFO_STRING(10, home_drive, 5, home_drive, "xx10-5 home_drive", 0); + TEST_USERINFO_STRING(10, home_drive, 21, home_drive, "xx10-21 home_drive", 0); + TEST_USERINFO_STRING(21, home_drive, 21, home_drive, "xx21-21 home_drive", + SAMR_FIELD_HOME_DRIVE); + TEST_USERINFO_STRING(21, home_drive, 10, home_drive, "xx21-10 home_drive", + SAMR_FIELD_HOME_DRIVE); + + TEST_USERINFO_STRING(13, description, 1, description, "xx13-1 description", 0); + TEST_USERINFO_STRING(13, description, 5, description, "xx13-5 description", 0); + TEST_USERINFO_STRING(13, description, 21, description, "xx13-21 description", 0); + TEST_USERINFO_STRING(21, description, 21, description, "xx21-21 description", + SAMR_FIELD_DESCRIPTION); + + TEST_USERINFO_STRING(14, workstations, 3, workstations, "14workstation3", 0); + TEST_USERINFO_STRING(14, workstations, 5, workstations, "14workstation4", 0); + TEST_USERINFO_STRING(14, workstations, 21, workstations, "14workstation21", 0); + TEST_USERINFO_STRING(21, workstations, 21, workstations, "21workstation21", + SAMR_FIELD_WORKSTATIONS); + TEST_USERINFO_STRING(21, workstations, 3, workstations, "21workstation3", + SAMR_FIELD_WORKSTATIONS); + TEST_USERINFO_STRING(21, workstations, 5, workstations, "21workstation5", + SAMR_FIELD_WORKSTATIONS); + TEST_USERINFO_STRING(21, workstations, 14, workstations, "21workstation14", + SAMR_FIELD_WORKSTATIONS); + + TEST_USERINFO_BINARYSTRING(20, parameters, 21, parameters, "xx20-21 parameters", 0); + TEST_USERINFO_BINARYSTRING(21, parameters, 21, parameters, "xx21-21 parameters", + SAMR_FIELD_PARAMETERS); + TEST_USERINFO_BINARYSTRING(21, parameters, 20, parameters, "xx21-20 parameters", + SAMR_FIELD_PARAMETERS); + /* also empty user parameters are allowed */ + TEST_USERINFO_BINARYSTRING(20, parameters, 21, parameters, "", 0); + TEST_USERINFO_BINARYSTRING(21, parameters, 21, parameters, "", + SAMR_FIELD_PARAMETERS); + TEST_USERINFO_BINARYSTRING(21, parameters, 20, parameters, "", + SAMR_FIELD_PARAMETERS); + + /* Samba 3 cannot store country_code and code_page atm. - gd */ + if (!torture_setting_bool(tctx, "samba3", false)) { + TEST_USERINFO_INT(2, country_code, 2, country_code, __LINE__, 0); + TEST_USERINFO_INT(2, country_code, 21, country_code, __LINE__, 0); + TEST_USERINFO_INT(21, country_code, 21, country_code, __LINE__, + SAMR_FIELD_COUNTRY_CODE); + TEST_USERINFO_INT(21, country_code, 2, country_code, __LINE__, + SAMR_FIELD_COUNTRY_CODE); + + TEST_USERINFO_INT(2, code_page, 21, code_page, __LINE__, 0); + TEST_USERINFO_INT(21, code_page, 21, code_page, __LINE__, + SAMR_FIELD_CODE_PAGE); + TEST_USERINFO_INT(21, code_page, 2, code_page, __LINE__, + SAMR_FIELD_CODE_PAGE); + } + + if (!torture_setting_bool(tctx, "samba3", false)) { + TEST_USERINFO_INT(17, acct_expiry, 21, acct_expiry, __LINE__, 0); + TEST_USERINFO_INT(17, acct_expiry, 5, acct_expiry, __LINE__, 0); + TEST_USERINFO_INT(21, acct_expiry, 21, acct_expiry, __LINE__, + SAMR_FIELD_ACCT_EXPIRY); + TEST_USERINFO_INT(21, acct_expiry, 5, acct_expiry, __LINE__, + SAMR_FIELD_ACCT_EXPIRY); + TEST_USERINFO_INT(21, acct_expiry, 17, acct_expiry, __LINE__, + SAMR_FIELD_ACCT_EXPIRY); + } else { + /* Samba 3 can only store seconds / time_t in passdb - gd */ + NTTIME nt; + unix_to_nt_time(&nt, time(NULL) + __LINE__); + TEST_USERINFO_INT(17, acct_expiry, 21, acct_expiry, nt, 0); + unix_to_nt_time(&nt, time(NULL) + __LINE__); + TEST_USERINFO_INT(17, acct_expiry, 5, acct_expiry, nt, 0); + unix_to_nt_time(&nt, time(NULL) + __LINE__); + TEST_USERINFO_INT(21, acct_expiry, 21, acct_expiry, nt, SAMR_FIELD_ACCT_EXPIRY); + unix_to_nt_time(&nt, time(NULL) + __LINE__); + TEST_USERINFO_INT(21, acct_expiry, 5, acct_expiry, nt, SAMR_FIELD_ACCT_EXPIRY); + unix_to_nt_time(&nt, time(NULL) + __LINE__); + TEST_USERINFO_INT(21, acct_expiry, 17, acct_expiry, nt, SAMR_FIELD_ACCT_EXPIRY); + } + + TEST_USERINFO_INT(4, logon_hours.bits[3], 3, logon_hours.bits[3], 1, 0); + TEST_USERINFO_INT(4, logon_hours.bits[3], 5, logon_hours.bits[3], 2, 0); + TEST_USERINFO_INT(4, logon_hours.bits[3], 21, logon_hours.bits[3], 3, 0); + TEST_USERINFO_INT(21, logon_hours.bits[3], 21, logon_hours.bits[3], 4, + SAMR_FIELD_LOGON_HOURS); + + TEST_USERINFO_INT_EXP(16, acct_flags, 5, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_HOMDIRREQ), + (base_acct_flags | ACB_DISABLED | ACB_HOMDIRREQ | user_extra_flags), + 0); + TEST_USERINFO_INT_EXP(16, acct_flags, 5, acct_flags, + (base_acct_flags | ACB_DISABLED), + (base_acct_flags | ACB_DISABLED | user_extra_flags), + 0); + + /* Setting PWNOEXP clears the magic ACB_PW_EXPIRED flag */ + TEST_USERINFO_INT_EXP(16, acct_flags, 5, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_PWNOEXP), + (base_acct_flags | ACB_DISABLED | ACB_PWNOEXP), + 0); + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_HOMDIRREQ), + (base_acct_flags | ACB_DISABLED | ACB_HOMDIRREQ | user_extra_flags), + 0); + + + /* The 'autolock' flag doesn't stick - check this */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_AUTOLOCK), + (base_acct_flags | ACB_DISABLED | user_extra_flags), + 0); +#if 0 + /* Removing the 'disabled' flag doesn't stick - check this */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags), + (base_acct_flags | ACB_DISABLED | user_extra_flags), + 0); +#endif + + /* Samba3 cannot store these atm */ + if (!torture_setting_bool(tctx, "samba3", false)) { + /* The 'store plaintext' flag does stick */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_ENC_TXT_PWD_ALLOWED), + (base_acct_flags | ACB_DISABLED | ACB_ENC_TXT_PWD_ALLOWED | user_extra_flags), + 0); + /* The 'use DES' flag does stick */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_USE_DES_KEY_ONLY), + (base_acct_flags | ACB_DISABLED | ACB_USE_DES_KEY_ONLY | user_extra_flags), + 0); + /* The 'don't require kerberos pre-authentication flag does stick */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_DONT_REQUIRE_PREAUTH), + (base_acct_flags | ACB_DISABLED | ACB_DONT_REQUIRE_PREAUTH | user_extra_flags), + 0); + /* The 'no kerberos PAC required' flag sticks */ + TEST_USERINFO_INT_EXP(16, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED | ACB_NO_AUTH_DATA_REQD), + (base_acct_flags | ACB_DISABLED | ACB_NO_AUTH_DATA_REQD | user_extra_flags), + 0); + } + TEST_USERINFO_INT_EXP(21, acct_flags, 21, acct_flags, + (base_acct_flags | ACB_DISABLED), + (base_acct_flags | ACB_DISABLED | user_extra_flags), + SAMR_FIELD_ACCT_FLAGS); + +#if 0 + /* these fail with win2003 - it appears you can't set the primary gid? + the set succeeds, but the gid isn't changed. Very weird! */ + TEST_USERINFO_INT(9, primary_gid, 1, primary_gid, 513); + TEST_USERINFO_INT(9, primary_gid, 3, primary_gid, 513); + TEST_USERINFO_INT(9, primary_gid, 5, primary_gid, 513); + TEST_USERINFO_INT(9, primary_gid, 21, primary_gid, 513); +#endif + + return ret; +} + +/* + generate a random password for password change tests +*/ +static char *samr_rand_pass_silent(TALLOC_CTX *mem_ctx, int min_len) +{ + size_t len = MAX(8, min_len); + char *s = generate_random_password(mem_ctx, len, len+6); + return s; +} + +static char *samr_rand_pass(TALLOC_CTX *mem_ctx, int min_len) +{ + char *s = samr_rand_pass_silent(mem_ctx, min_len); + printf("Generated password '%s'\n", s); + return s; + +} + +/* + generate a random password for password change tests +*/ +static DATA_BLOB samr_very_rand_pass(TALLOC_CTX *mem_ctx, int len) +{ + int i; + DATA_BLOB password = data_blob_talloc(mem_ctx, NULL, len * 2 /* number of unicode chars */); + generate_random_buffer(password.data, password.length); + + for (i=0; i < len; i++) { + if (((uint16_t *)password.data)[i] == 0) { + ((uint16_t *)password.data)[i] = 1; + } + } + + return password; +} + +/* + generate a random password for password change tests (fixed length) +*/ +static char *samr_rand_pass_fixed_len(TALLOC_CTX *mem_ctx, int len) +{ + char *s = generate_random_password(mem_ctx, len, len); + printf("Generated password '%s'\n", s); + return s; +} + +static bool test_SetUserPass(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 24; + + u.info24.password_expired = 0; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + status = init_samr_CryptPassword(newpass, + &session_key, + &u.info24.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + torture_comment(tctx, "Testing SetUserInfo level 24 (set password)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + + +static bool test_SetUserPass_23(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, uint32_t fields_present, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + struct dcerpc_binding_handle *b = p->binding_handle; + char *newpass; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 23; + + ZERO_STRUCT(u); + + u.info23.info.fields_present = fields_present; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + status = init_samr_CryptPassword(newpass, + &session_key, + &u.info23.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + torture_comment(tctx, "Testing SetUserInfo level 23 (set password)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + /* This should break the key nicely */ + session_key.data[0]++; + + status = init_samr_CryptPassword(newpass, + &session_key, + &u.info23.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + /* Reset the session key */ + session_key.data[0]--; + + torture_comment(tctx, "Testing SetUserInfo level 23 (set password) with wrong password\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with WRONG_PASSWORD- %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } + + return ret; +} + +static bool test_SetUserPass_32(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, uint32_t fields_present, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + DATA_BLOB session_key; + uint8_t salt_data[16]; + DATA_BLOB salt = { + .data = salt_data, + .length = sizeof(salt_data), + }; + char *newpass = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + bool ret = true; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 32; + + ZERO_STRUCT(u); + + u.info32.info.fields_present = fields_present; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, + TORTURE_FAIL, + "SetUserInfo level %u - no session key - %s\n", + s.in.level, + nt_errstr(status)); + return false; + } + + generate_nonce_buffer(salt.data, salt.length); + + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info32.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordAES failed"); + + torture_comment(tctx, + "Testing SetUserInfo level 32 (set password aes)\n"); + + status = dcerpc_samr_SetUserInfo_r(b, tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, + __FUNCTION__, + newpass, + nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, + TORTURE_FAIL, + "SetUserInfo level %u failed - %s\n", + s.in.level, + nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + /* This should break the key nicely */ + session_key.data[0]++; + + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info32.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + /* Reset the key */ + session_key.data[0]--; + + torture_comment(tctx, + "Testing SetUserInfo level 32 (set password aes) with " + "wrong session key\n"); + + status = dcerpc_samr_SetUserInfo_r(b, tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "SetUserInfo failed"); + torture_comment(tctx, + "(%s:%s) new_password[%s] status[%s]\n", + __location__, + __FUNCTION__, + newpass, + nt_errstr(s.out.result)); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, + TORTURE_FAIL, + "SetUserInfo level %u should have failed with " + "WRONG_PASSWORD- %s\n", + s.in.level, + nt_errstr(s.out.result)); + ret = false; + } + + return ret; +} + + +static bool test_SetUserPass_31(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, bool makeshort, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + uint8_t salt_data[16]; + DATA_BLOB salt = { + .data = salt_data, + .length = sizeof(salt_data), + }; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + if (makeshort && policy_min_pw_len) { + newpass = samr_rand_pass_fixed_len(tctx, policy_min_pw_len - 1); + } else { + newpass = samr_rand_pass(tctx, policy_min_pw_len); + } + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 31; + + ZERO_STRUCT(u); + + u.info31.password_expired = 0; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + generate_nonce_buffer(salt.data, salt.length); + + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info31.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + torture_comment(tctx, "Testing SetUserInfo level 31 (set password aes)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + /* This should break the key nicely */ + session_key.data[0]++; + + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info31.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + /* Reset the key */ + session_key.data[0]--; + + torture_comment(tctx, "Testing SetUserInfo level 31 (set password aes) with wrong session key\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with WRONG_PASSWORD: %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + + +static bool test_SetUserPassEx(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, bool makeshort, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + if (makeshort && policy_min_pw_len) { + newpass = samr_rand_pass_fixed_len(tctx, policy_min_pw_len - 1); + } else { + newpass = samr_rand_pass(tctx, policy_min_pw_len); + } + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 26; + + u.info26.password_expired = 0; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info26.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + torture_comment(tctx, "Testing SetUserInfo level 26 (set password ex)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + /* This should break the key nicely */ + session_key.data[0]++; + + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info26.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + /* Reset the key */ + session_key.data[0]--; + + torture_comment(tctx, "Testing SetUserInfo level 26 (set password ex) with wrong session key\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with WRONG_PASSWORD: %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + +static bool test_SetUserPass_25(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, uint32_t fields_present, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 25; + + ZERO_STRUCT(u); + + u.info25.info.fields_present = fields_present; + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info25.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + torture_comment(tctx, "Testing SetUserInfo level 25 (set password ex)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + /* This should break the key nicely */ + session_key.data[0]++; + + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info25.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + + /* Reset the key */ + session_key.data[0]--; + + torture_comment(tctx, "Testing SetUserInfo level 25 (set password ex) with wrong session key\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with WRONG_PASSWORD- %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } + + return ret; +} + +static bool test_SetUserPass_18(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + uint8_t lm_hash[16], nt_hash[16]; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 18; + + ZERO_STRUCT(u); + + u.info18.nt_pwd_active = true; + u.info18.lm_pwd_active = true; + + E_md4hash(newpass, nt_hash); + E_deshash(newpass, lm_hash); + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + { + DATA_BLOB in,out; + in = data_blob_const(nt_hash, 16); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + memcpy(u.info18.nt_pwd.hash, out.data, out.length); + } + { + DATA_BLOB in,out; + in = data_blob_const(lm_hash, 16); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + memcpy(u.info18.lm_pwd.hash, out.data, out.length); + } + + torture_comment(tctx, "Testing SetUserInfo level 18 (set password hash)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + +static bool test_SetUserPass_21(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, uint32_t fields_present, + char **password) +{ + NTSTATUS status; + struct samr_SetUserInfo s; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + uint8_t lm_hash[16], nt_hash[16]; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 21; + + E_md4hash(newpass, nt_hash); + E_deshash(newpass, lm_hash); + + ZERO_STRUCT(u); + + u.info21.fields_present = fields_present; + + if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + u.info21.lm_owf_password.length = 16; + u.info21.lm_owf_password.size = 16; + u.info21.lm_owf_password.array = (uint16_t *)lm_hash; + u.info21.lm_password_set = true; + } + + if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + u.info21.nt_owf_password.length = 16; + u.info21.nt_owf_password.size = 16; + u.info21.nt_owf_password.array = (uint16_t *)nt_hash; + u.info21.nt_password_set = true; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + DATA_BLOB in,out; + in = data_blob_const(u.info21.lm_owf_password.array, + u.info21.lm_owf_password.length); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + u.info21.lm_owf_password.array = (uint16_t *)out.data; + } + + if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + DATA_BLOB in,out; + in = data_blob_const(u.info21.nt_owf_password.array, + u.info21.nt_owf_password.length); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + u.info21.nt_owf_password.array = (uint16_t *)out.data; + } + + torture_comment(tctx, "Testing SetUserInfo level 21 (set password hash)\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } else { + *password = newpass; + } + + /* try invalid length */ + if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + + u.info21.nt_owf_password.length++; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with NT_STATUS_INVALID_PARAMETER - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } + } + + if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + + u.info21.lm_owf_password.length++; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + if (!NT_STATUS_EQUAL(s.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u should have failed with NT_STATUS_INVALID_PARAMETER - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_SetUserPass_level_ex(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint16_t level, + uint32_t fields_present, + char **password, uint8_t password_expired, + bool use_setinfo2, + bool *matched_expected_error) +{ + NTSTATUS status; + NTSTATUS expected_error = NT_STATUS_OK; + struct samr_SetUserInfo s; + struct samr_SetUserInfo2 s2; + union samr_UserInfo u; + bool ret = true; + DATA_BLOB session_key; + uint8_t salt_data[16]; + DATA_BLOB salt = { + .data = salt_data, + .length = sizeof(salt_data), + }; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + const char *comment = NULL; + uint8_t lm_hash[16], nt_hash[16]; + + pwp.in.user_handle = handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass_silent(tctx, policy_min_pw_len); + + if (use_setinfo2) { + s2.in.user_handle = handle; + s2.in.info = &u; + s2.in.level = level; + } else { + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = level; + } + + if (fields_present & SAMR_FIELD_COMMENT) { + comment = talloc_asprintf(tctx, "comment: %ld\n", (long int) time(NULL)); + } + + ZERO_STRUCT(u); + + switch (level) { + case 18: + E_md4hash(newpass, nt_hash); + E_deshash(newpass, lm_hash); + + u.info18.nt_pwd_active = true; + u.info18.lm_pwd_active = true; + u.info18.password_expired = password_expired; + + memcpy(u.info18.lm_pwd.hash, lm_hash, 16); + memcpy(u.info18.nt_pwd.hash, nt_hash, 16); + + break; + case 21: + E_md4hash(newpass, nt_hash); + E_deshash(newpass, lm_hash); + + u.info21.fields_present = fields_present; + u.info21.password_expired = password_expired; + u.info21.comment.string = comment; + + if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + u.info21.lm_owf_password.length = 16; + u.info21.lm_owf_password.size = 16; + u.info21.lm_owf_password.array = (uint16_t *)lm_hash; + u.info21.lm_password_set = true; + } + + if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + u.info21.nt_owf_password.length = 16; + u.info21.nt_owf_password.size = 16; + u.info21.nt_owf_password.array = (uint16_t *)nt_hash; + u.info21.nt_password_set = true; + } + + break; + case 23: + u.info23.info.fields_present = fields_present; + u.info23.info.password_expired = password_expired; + u.info23.info.comment.string = comment; + + break; + case 24: + u.info24.password_expired = password_expired; + + break; + case 25: + u.info25.info.fields_present = fields_present; + u.info25.info.password_expired = password_expired; + u.info25.info.comment.string = comment; + + break; + case 26: + u.info26.password_expired = password_expired; + + break; + case 31: + u.info31.password_expired = password_expired; + + break; + case 28: + u.info25.info.fields_present = fields_present; + u.info25.info.password_expired = password_expired; + u.info25.info.comment.string = comment; + + break; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + generate_nonce_buffer(salt.data, salt.length); + + switch (level) { + case 18: + { + DATA_BLOB in,out; + in = data_blob_const(u.info18.nt_pwd.hash, 16); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + memcpy(u.info18.nt_pwd.hash, out.data, out.length); + } + { + DATA_BLOB in,out; + in = data_blob_const(u.info18.lm_pwd.hash, 16); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + memcpy(u.info18.lm_pwd.hash, out.data, out.length); + } + + break; + case 21: + if (fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) { + DATA_BLOB in,out; + in = data_blob_const(u.info21.lm_owf_password.array, + u.info21.lm_owf_password.length); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + u.info21.lm_owf_password.array = (uint16_t *)out.data; + } + if (fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) { + DATA_BLOB in,out; + in = data_blob_const(u.info21.nt_owf_password.array, + u.info21.nt_owf_password.length); + out = data_blob_talloc_zero(tctx, 16); + sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT); + u.info21.nt_owf_password.array = (uint16_t *)out.data; + } + break; + case 23: + status = init_samr_CryptPassword(newpass, + &session_key, + &u.info23.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + break; + case 24: + status = init_samr_CryptPassword(newpass, + &session_key, + &u.info24.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + break; + case 25: + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info25.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + break; + case 26: + status = init_samr_CryptPasswordEx(newpass, + &session_key, + &u.info26.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPasswordEx failed"); + break; + case 31: + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info31.password); + + break; + case 32: + status = init_samr_CryptPasswordAES(tctx, + newpass, + &salt, + &session_key, + &u.info32.password); + + break; + } + + if (use_setinfo2) { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo2_r(b, tctx, &s2), + "SetUserInfo2 failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s2.out.result)); + status = s2.out.result; + } else { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + newpass, nt_errstr(s.out.result)); + status = s.out.result; + } + + if (!NT_STATUS_IS_OK(status)) { + if (fields_present == 0) { + expected_error = NT_STATUS_INVALID_PARAMETER; + } + if (fields_present & SAMR_FIELD_LAST_PWD_CHANGE) { + expected_error = NT_STATUS_ACCESS_DENIED; + } + } + + if (!NT_STATUS_IS_OK(expected_error)) { + if (use_setinfo2) { + torture_assert_ntstatus_equal(tctx, + s2.out.result, + expected_error, "SetUserInfo2 failed"); + } else { + torture_assert_ntstatus_equal(tctx, + s.out.result, + expected_error, "SetUserInfo failed"); + } + *matched_expected_error = true; + return true; + } + + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo%s level %u failed - %s\n", + use_setinfo2 ? "2":"", level, nt_errstr(status)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + +static bool test_SetAliasInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_SetAliasInfo r; + struct samr_QueryAliasInfo q; + union samr_AliasInfo *info; + uint16_t levels[] = {2, 3}; + int i; + bool ret = true; + + /* Ignoring switch level 1, as that includes the number of members for the alias + * and setting this to a wrong value might have negative consequences + */ + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing SetAliasInfo level %u\n", levels[i]); + + r.in.alias_handle = handle; + r.in.level = levels[i]; + r.in.info = talloc(tctx, union samr_AliasInfo); + switch (r.in.level) { + case ALIASINFONAME: init_lsa_String(&r.in.info->name,TEST_ALIASNAME); break; + case ALIASINFODESCRIPTION: init_lsa_String(&r.in.info->description, + "Test Description, should test I18N as well"); break; + case ALIASINFOALL: torture_comment(tctx, "ALIASINFOALL ignored\n"); break; + } + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetAliasInfo_r(b, tctx, &r), + "SetAliasInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetAliasInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + + q.in.alias_handle = handle; + q.in.level = levels[i]; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryAliasInfo_r(b, tctx, &q), + "QueryAliasInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryAliasInfo level %u failed - %s\n", + levels[i], nt_errstr(q.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_GetGroupsForUser(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *user_handle) +{ + struct samr_GetGroupsForUser r; + struct samr_RidWithAttributeArray *rids = NULL; + + torture_comment(tctx, "Testing GetGroupsForUser\n"); + + r.in.user_handle = user_handle; + r.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetGroupsForUser_r(b, tctx, &r), + "GetGroupsForUser failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetGroupsForUser failed"); + + return true; + +} + +static bool test_GetDomPwInfo(struct dcerpc_pipe *p, struct torture_context *tctx, + struct lsa_String *domain_name) +{ + struct samr_GetDomPwInfo r; + struct samr_PwInfo info; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.domain_name = domain_name; + r.out.info = &info; + + torture_comment(tctx, "Testing GetDomPwInfo with name %s\n", r.in.domain_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &r), + "GetDomPwInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetDomPwInfo failed"); + + r.in.domain_name->string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_comment(tctx, "Testing GetDomPwInfo with name %s\n", r.in.domain_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &r), + "GetDomPwInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetDomPwInfo failed"); + + r.in.domain_name->string = "\\\\__NONAME__"; + torture_comment(tctx, "Testing GetDomPwInfo with name %s\n", r.in.domain_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &r), + "GetDomPwInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetDomPwInfo failed"); + + r.in.domain_name->string = "\\\\Builtin"; + torture_comment(tctx, "Testing GetDomPwInfo with name %s\n", r.in.domain_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &r), + "GetDomPwInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetDomPwInfo failed"); + + return true; +} + +static bool test_GetUserPwInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_GetUserPwInfo r; + struct samr_PwInfo info; + + torture_comment(tctx, "Testing GetUserPwInfo\n"); + + r.in.user_handle = handle; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &r), + "GetUserPwInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetUserPwInfo"); + + return true; +} + +static NTSTATUS test_LookupName(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, const char *name, + uint32_t *rid) +{ + NTSTATUS status; + struct samr_LookupNames n; + struct lsa_String sname[2]; + struct samr_Ids rids, types; + + init_lsa_String(&sname[0], name); + + n.in.domain_handle = domain_handle; + n.in.num_names = 1; + n.in.names = sname; + n.out.rids = &rids; + n.out.types = &types; + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (NT_STATUS_IS_OK(n.out.result)) { + *rid = n.out.rids->ids[0]; + } else { + return n.out.result; + } + + init_lsa_String(&sname[1], "xxNONAMExx"); + n.in.num_names = 2; + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_EQUAL(n.out.result, STATUS_SOME_UNMAPPED)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames[2] failed - %s\n", nt_errstr(n.out.result)); + if (NT_STATUS_IS_OK(n.out.result)) { + return NT_STATUS_UNSUCCESSFUL; + } + return n.out.result; + } + + n.in.num_names = 0; + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(n.out.result)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames[0] failed - %s\n", nt_errstr(status)); + return n.out.result; + } + + init_lsa_String(&sname[0], "xxNONAMExx"); + n.in.num_names = 1; + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_EQUAL(n.out.result, NT_STATUS_NONE_MAPPED)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames[1 bad name] failed - %s\n", nt_errstr(n.out.result)); + if (NT_STATUS_IS_OK(n.out.result)) { + return NT_STATUS_UNSUCCESSFUL; + } + return n.out.result; + } + + init_lsa_String(&sname[0], "xxNONAMExx"); + init_lsa_String(&sname[1], "xxNONAME2xx"); + n.in.num_names = 2; + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_EQUAL(n.out.result, NT_STATUS_NONE_MAPPED)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames[2 bad names] failed - %s\n", nt_errstr(n.out.result)); + if (NT_STATUS_IS_OK(n.out.result)) { + return NT_STATUS_UNSUCCESSFUL; + } + return n.out.result; + } + + return NT_STATUS_OK; +} + +static NTSTATUS test_OpenUser_byname(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *name, struct policy_handle *user_handle) +{ + NTSTATUS status; + struct samr_OpenUser r; + uint32_t rid; + + status = test_LookupName(b, tctx, domain_handle, name, &rid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r.in.domain_handle = domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.user_handle = user_handle; + status = dcerpc_samr_OpenUser_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenUser_byname(%s -> %d) failed - %s\n", name, rid, nt_errstr(r.out.result)); + } + + return r.out.result; +} + +#if 0 +static bool test_ChangePasswordNT3(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle) +{ + NTSTATUS status; + struct samr_ChangePasswordUser r; + bool ret = true; + struct samr_Password hash1, hash2, hash3, hash4, hash5, hash6; + struct policy_handle user_handle; + char *oldpass = "test"; + char *newpass = "test2"; + uint8_t old_nt_hash[16], new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + + status = test_OpenUser_byname(p, tctx, handle, "testuser", &user_handle); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + torture_comment(tctx, "Testing ChangePasswordUser for user 'testuser'\n"); + + torture_comment(tctx, "old password: %s\n", oldpass); + torture_comment(tctx, "new password: %s\n", newpass); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash); + E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash); + E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash); + E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash); + E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash); + E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash); + + r.in.handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + if (!test_samr_handle_Close(p, tctx, &user_handle)) { + ret = false; + } + + return ret; +} +#endif + +static bool test_ChangePasswordUser(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + const char *acct_name, + struct policy_handle *handle, char **password) +{ + NTSTATUS status; + struct samr_ChangePasswordUser r; + bool ret = true; + struct samr_Password hash1, hash2, hash3, hash4, hash5, hash6; + struct policy_handle user_handle; + char *oldpass; + uint8_t old_nt_hash[16], new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + bool changed = true; + + char *newpass; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + + status = test_OpenUser_byname(b, tctx, handle, acct_name, &user_handle); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + pwp.in.user_handle = &user_handle; + pwp.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetUserPwInfo_r(b, tctx, &pwp), + "GetUserPwInfo failed"); + if (NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + torture_comment(tctx, "Testing ChangePasswordUser\n"); + + torture_assert(tctx, *password != NULL, + "Failing ChangePasswordUser as old password was NULL. Previous test failed?"); + + oldpass = *password; + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash); + E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash); + E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash); + E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash); + E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash); + E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash); + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + /* Break the NT hash */ + hash3.hash[0]++; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + /* Do not proceed if this call has been removed */ + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_NOT_IMPLEMENTED)) { + torture_skip(tctx, "ValidatePassword not supported by server\n"); + } + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, + "ChangePasswordUser failed: expected NT_STATUS_WRONG_PASSWORD because we broke the LM hash"); + } + + /* Unbreak the NT hash */ + hash3.hash[0]--; + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + /* Break the LM hash */ + hash1.hash[0]--; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_WRONG_PASSWORD, + "expected NT_STATUS_WRONG_PASSWORD because we broke the NT hash"); + } + + /* Unbreak the NT hash */ + hash3.hash[0]--; + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + /* Break the LM cross */ + hash6.hash[0]++; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD) && + !NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) + { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed: expected NT_STATUS_WRONG_PASSWORD or NT_STATUS_PASSWORD_RESTRICTION because we broke the LM cross-hash, got %s\n", nt_errstr(r.out.result)); + ret = false; + } + + /* Unbreak the LM cross */ + hash6.hash[0]--; + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + /* Break the NT cross */ + hash5.hash[0]++; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD) && + !NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) + { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed: expected NT_STATUS_WRONG_PASSWORD or NT_STATUS_PASSWORD_RESTRICTION because we broke the NT cross-hash, got %s\n", nt_errstr(r.out.result)); + ret = false; + } + + /* Unbreak the NT cross */ + hash5.hash[0]--; + + + /* Reset the hashes to not broken values */ + E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash); + E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash); + E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash); + E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash); + E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash); + E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash); + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 0; + r.in.lm_cross = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (NT_STATUS_IS_OK(r.out.result)) { + changed = true; + *password = newpass; + } else if (!NT_STATUS_EQUAL(NT_STATUS_PASSWORD_RESTRICTION, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed: expected NT_STATUS_OK, or at least NT_STATUS_PASSWORD_RESTRICTION, got %s\n", nt_errstr(r.out.result)); + ret = false; + } + + oldpass = newpass; + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + + /* Reset the hashes to not broken values */ + E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash); + E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash); + E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash); + E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash); + E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash); + E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash); + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 0; + r.in.nt_cross = NULL; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (NT_STATUS_IS_OK(r.out.result)) { + changed = true; + *password = newpass; + } else if (!NT_STATUS_EQUAL(NT_STATUS_PASSWORD_RESTRICTION, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed: expected NT_STATUS_OK, or at least NT_STATUS_PASSWORD_RESTRICTION, got %s\n", nt_errstr(r.out.result)); + ret = false; + } + + oldpass = newpass; + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + + /* Reset the hashes to not broken values */ + E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash); + E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash); + E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash); + E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash); + E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash); + E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash); + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_comment(tctx, "ChangePasswordUser returned: %s perhaps min password age? (not fatal)\n", nt_errstr(r.out.result)); + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } else { + changed = true; + *password = newpass; + } + + r.in.user_handle = &user_handle; + r.in.lm_present = 1; + r.in.old_lm_crypted = &hash1; + r.in.new_lm_crypted = &hash2; + r.in.nt_present = 1; + r.in.old_nt_crypted = &hash3; + r.in.new_nt_crypted = &hash4; + r.in.cross1_present = 1; + r.in.nt_cross = &hash5; + r.in.cross2_present = 1; + r.in.lm_cross = &hash6; + + if (changed) { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser_r(b, tctx, &r), + "ChangePasswordUser failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_comment(tctx, "ChangePasswordUser returned: %s perhaps min password age? (not fatal)\n", nt_errstr(r.out.result)); + } else if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser failed: expected NT_STATUS_WRONG_PASSWORD because we already changed the password, got %s\n", nt_errstr(r.out.result)); + ret = false; + } + } + + + if (!test_samr_handle_Close(b, tctx, &user_handle)) { + ret = false; + } + + return ret; +} + + +static bool test_OemChangePasswordUser2(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *acct_name, + struct policy_handle *handle, char **password) +{ + struct samr_OemChangePasswordUser2 r; + bool ret = true; + struct samr_Password lm_verifier; + struct samr_CryptPassword lm_pass; + struct lsa_AsciiString server, account, account_bad; + char *oldpass; + char *newpass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t old_lm_hash[16], new_lm_hash[16]; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t session_key = { + .data = old_lm_hash, + .size = 16 + }; + + struct samr_GetDomPwInfo dom_pw_info; + struct samr_PwInfo info; + int policy_min_pw_len = 0; + + struct lsa_String domain_name; + + domain_name.string = ""; + dom_pw_info.in.domain_name = &domain_name; + dom_pw_info.out.info = &info; + + torture_comment(tctx, "Testing OemChangePasswordUser2\n"); + + torture_assert(tctx, *password != NULL, + "Failing OemChangePasswordUser2 as old password was NULL. Previous test failed?"); + + oldpass = *password; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &dom_pw_info), + "GetDomPwInfo failed"); + if (NT_STATUS_IS_OK(dom_pw_info.out.result)) { + policy_min_pw_len = dom_pw_info.out.info->min_password_length; + } + + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + server.string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + account.string = acct_name; + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII); + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, lm_pass.data, 516); + gnutls_cipher_deinit(cipher_hnd); + E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.password = &lm_pass; + r.in.hash = &lm_verifier; + + /* Break the verification */ + lm_verifier.hash[0]++; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_assert_ntstatus_equal(tctx, + r.out.result, + NT_STATUS_NOT_IMPLEMENTED, + "Samba4 should refuse LM password change"); + /* + * No point continuing, once we have checked this is not + * implemented + */ + return true; + } + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) + && !NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned WRONG_PASSWORD (or at least 'PASSWORD_RESTRICTON') for invalid password verifier - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII); + /* Break the old password */ + old_lm_hash[0]++; + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, lm_pass.data, 516); + gnutls_cipher_deinit(cipher_hnd); + /* unbreak it for the next operation */ + old_lm_hash[0]--; + E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.password = &lm_pass; + r.in.hash = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) + && !NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned WRONG_PASSWORD (or at least 'PASSWORD_RESTRICTON') for invalidly encrypted password - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII); + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, lm_pass.data, 516); + gnutls_cipher_deinit(cipher_hnd); + + r.in.server = &server; + r.in.account = &account; + r.in.password = &lm_pass; + r.in.hash = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) + && !NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned INVALID_PARAMETER (or at least 'PASSWORD_RESTRICTON') for no supplied validation hash - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + /* This shouldn't be a valid name */ + account_bad.string = TEST_ACCOUNT_NAME "XX"; + r.in.account = &account_bad; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned INVALID_PARAMETER for no supplied validation hash and invalid user - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + /* This shouldn't be a valid name */ + account_bad.string = TEST_ACCOUNT_NAME "XX"; + r.in.account = &account_bad; + r.in.password = &lm_pass; + r.in.hash = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned WRONG_PASSWORD for invalid user - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + /* This shouldn't be a valid name */ + account_bad.string = TEST_ACCOUNT_NAME "XX"; + r.in.account = &account_bad; + r.in.password = NULL; + r.in.hash = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed, should have returned INVALID_PARAMETER for no supplied password and invalid user - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII); + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &session_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, lm_pass.data, 516); + gnutls_cipher_deinit(cipher_hnd); + E_old_pw_hash(new_lm_hash, old_lm_hash, lm_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.password = &lm_pass; + r.in.hash = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OemChangePasswordUser2_r(b, tctx, &r), + "OemChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_comment(tctx, "OemChangePasswordUser2 returned: %s perhaps min password age? (not fatal)\n", nt_errstr(r.out.result)); + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OemChangePasswordUser2 failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + + +static bool test_ChangePasswordUser2(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *acct_name, + char **password, + char *newpass, bool allow_password_restriction) +{ + struct samr_ChangePasswordUser2 r; + bool ret = true; + struct lsa_String server, account; + struct samr_CryptPassword nt_pass, lm_pass; + struct samr_Password nt_verifier, lm_verifier; + char *oldpass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t old_nt_hash[16] = { 0 }, new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + DATA_BLOB old_nt_hash_blob + = data_blob_const(old_nt_hash, sizeof(old_nt_hash)); + struct samr_GetDomPwInfo dom_pw_info; + struct samr_PwInfo info; + + struct lsa_String domain_name; + NTSTATUS status; + + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t old_lm_key = { + .data = old_lm_hash, + .size = sizeof(old_lm_hash), + }; + + domain_name.string = ""; + dom_pw_info.in.domain_name = &domain_name; + dom_pw_info.out.info = &info; + + torture_comment(tctx, "Testing ChangePasswordUser2 on %s\n", acct_name); + + torture_assert(tctx, *password != NULL, + "Failing ChangePasswordUser2 as old password was NULL. Previous test failed?"); + oldpass = *password; + + if (!newpass) { + int policy_min_pw_len = 0; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &dom_pw_info), + "GetDomPwInfo failed"); + if (NT_STATUS_IS_OK(dom_pw_info.out.result)) { + policy_min_pw_len = dom_pw_info.out.info->min_password_length; + } + + newpass = samr_rand_pass(tctx, policy_min_pw_len); + } + + server.string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + init_lsa_String(&account, acct_name); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII|STR_TERMINATE); + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &old_lm_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, + lm_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + + E_old_pw_hash(new_nt_hash, old_lm_hash, lm_verifier.hash); + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 1; + r.in.lm_password = &lm_pass; + r.in.lm_verifier = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser2_r(b, tctx, &r), + "ChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (allow_password_restriction && NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_comment(tctx, "ChangePasswordUser2 returned: %s perhaps min password age? (not fatal)\n", nt_errstr(r.out.result)); + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser2 failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } else { + *password = newpass; + } + + return ret; +} + + +static bool test_ChangePasswordUser2_ntstatus(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *acct_name, + const char *password, NTSTATUS status) +{ + struct samr_ChangePasswordUser2 r; + struct lsa_String server, account; + struct samr_CryptPassword nt_pass, lm_pass; + struct samr_Password nt_verifier, lm_verifier; + const char *oldpass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t old_nt_hash[16] = { 0 }, new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + DATA_BLOB old_nt_hash_blob + = data_blob_const(old_nt_hash, sizeof(old_nt_hash)); + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t old_lm_key = { + .data = old_lm_hash, + .size = sizeof(old_lm_hash), + }; + + struct samr_GetDomPwInfo dom_pw_info; + struct samr_PwInfo info; + + struct lsa_String domain_name; + NTSTATUS crypt_status; + + char *newpass; + int policy_min_pw_len = 0; + + domain_name.string = ""; + dom_pw_info.in.domain_name = &domain_name; + dom_pw_info.out.info = &info; + + torture_comment(tctx, "Testing ChangePasswordUser2 on %s\n", acct_name); + + oldpass = password; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &dom_pw_info), + "GetDomPwInfo failed"); + if (NT_STATUS_IS_OK(dom_pw_info.out.result)) { + policy_min_pw_len = dom_pw_info.out.info->min_password_length; + } + + newpass = samr_rand_pass(tctx, policy_min_pw_len); + + server.string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + init_lsa_String(&account, acct_name); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + encode_pw_buffer(lm_pass.data, newpass, STR_ASCII|STR_TERMINATE); + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &old_lm_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, + lm_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + + E_old_pw_hash(new_nt_hash, old_lm_hash, lm_verifier.hash); + + crypt_status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + crypt_status, + "init_samr_CryptPassword failed"); + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 1; + r.in.lm_password = &lm_pass; + r.in.lm_verifier = &lm_verifier; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser2_r(b, tctx, &r), + "ChangePasswordUser2 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + torture_comment(tctx, "ChangePasswordUser2 returned: %s perhaps min password age? (not fatal)\n", nt_errstr(r.out.result)); + } else { + torture_assert_ntstatus_equal(tctx, r.out.result, status, "ChangePasswordUser2 returned unexpected value"); + } + + return true; +} + + +bool test_ChangePasswordUser3(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *account_string, + int policy_min_pw_len, + char **password, + const char *newpass, + NTTIME last_password_change, + bool handle_reject_reason) +{ + struct samr_ChangePasswordUser3 r; + bool ret = true; + struct lsa_String server, account, account_bad; + struct samr_CryptPassword nt_pass, lm_pass; + struct samr_Password nt_verifier, lm_verifier; + char *oldpass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t old_nt_hash[16] = { 0 }, new_nt_hash[16]; + uint8_t old_lm_hash[16], new_lm_hash[16]; + NTTIME t; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + DATA_BLOB old_nt_hash_blob = data_blob_const(old_nt_hash, 16); + NTSTATUS status; + + torture_comment(tctx, "Testing ChangePasswordUser3\n"); + + if (newpass == NULL) { + do { + if (policy_min_pw_len == 0) { + newpass = samr_rand_pass(tctx, policy_min_pw_len); + } else { + newpass = samr_rand_pass_fixed_len(tctx, policy_min_pw_len); + } + } while (check_password_quality(newpass) == false); + } else { + torture_comment(tctx, "Using password '%s'\n", newpass); + } + + torture_assert(tctx, *password != NULL, + "Failing ChangePasswordUser3 as old password was NULL. Previous test failed?"); + + oldpass = *password; + server.string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + init_lsa_String(&account, account_string); + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + /* + * The new plaintext password is encrypted using RC4 with the + * old NT password hash (directly, with no confounder). The + * password is at the end of the random padded buffer, + * offering a little protection. + * + * This is almost certainly wrong, it should be the old LM + * hash, it was switched in an unrelated commit + * 579c13da43d5b40ac6d6c1436399fbc1d8dfd054 in 2004. + */ + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &lm_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + /* + * Now we prepare a DES cross-hash of the old LM and new NT + * passwords to link the two buffers + */ + E_old_pw_hash(new_nt_hash, old_lm_hash, lm_verifier.hash); + + /* + * The new plaintext password is also encrypted using RC4 with + * the old NT password hash (directly, with no confounder). + * The password is at the end of the random padded buffer, + * offering a little protection. + */ + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + /* + * Another DES based cross-hash + */ + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + /* Break the verification */ + nt_verifier.hash[0]++; + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 1; + r.in.lm_password = &lm_pass; + r.in.lm_verifier = &lm_verifier; + r.in.password3 = NULL; + r.out.dominfo = &dominfo; + r.out.reject = &reject; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) && + (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD))) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser3 failed, should have returned WRONG_PASSWORD (or at least 'PASSWORD_RESTRICTON') for invalid password verifier - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &lm_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + E_old_pw_hash(new_nt_hash, old_lm_hash, lm_verifier.hash); + + /* Break the NT Hash */ + old_nt_hash[0]++; + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + /* Unbreak it again */ + old_nt_hash[0]--; + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 1; + r.in.lm_password = &lm_pass; + r.in.lm_verifier = &lm_verifier; + r.in.password3 = NULL; + r.out.dominfo = &dominfo; + r.out.reject = &reject; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) && + (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD))) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser3 failed, should have returned WRONG_PASSWORD (or at least 'PASSWORD_RESTRICTON') for invalidly encrypted password - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + /* This shouldn't be a valid name */ + init_lsa_String(&account_bad, talloc_asprintf(tctx, "%sXX", account_string)); + + r.in.account = &account_bad; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_WRONG_PASSWORD)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser3 failed, should have returned WRONG_PASSWORD for invalid username - %s\n", + nt_errstr(r.out.result)); + ret = false; + } + + E_md4hash(oldpass, old_nt_hash); + E_md4hash(newpass, new_nt_hash); + + E_deshash(oldpass, old_lm_hash); + E_deshash(newpass, new_lm_hash); + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &lm_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + E_old_pw_hash(new_nt_hash, old_lm_hash, lm_verifier.hash); + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword"); + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 1; + r.in.lm_password = &lm_pass; + r.in.lm_verifier = &lm_verifier; + r.in.password3 = NULL; + r.out.dominfo = &dominfo; + r.out.reject = &reject; + + unix_to_nt_time(&t, time(NULL)); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + torture_comment(tctx, "(%s): dominfo[%s], reject[%s], handle_reject_reason[%s], " + "last_password_change[%s], dominfo->min_password_age[%lld]\n", + __location__, + (dominfo == NULL)? "NULL" : "present", + reject ? "true" : "false", + handle_reject_reason ? "true" : "false", + null_nttime(last_password_change) ? "null" : "not null", + dominfo ? (long long)dominfo->min_password_age : (long long)0); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION) + && dominfo + && reject + && handle_reject_reason + && (!null_nttime(last_password_change) || !dominfo->min_password_age)) { + if (dominfo->password_properties & DOMAIN_REFUSE_PASSWORD_CHANGE ) { + + if (reject && (reject->extendedFailureReason != SAM_PWD_CHANGE_NO_ERROR)) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NO_ERROR (%d), got %d\n", + SAM_PWD_CHANGE_NO_ERROR, reject->extendedFailureReason); + return false; + } + } + + /* We tested the order of precendence which is as follows: + + * pwd min_age + * pwd length + * pwd complexity + * pwd history + + Guenther */ + + if ((dominfo->min_password_age < 0) && !null_nttime(last_password_change) && + (last_password_change - dominfo->min_password_age > t)) { + + if (reject->extendedFailureReason != SAM_PWD_CHANGE_NO_ERROR) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NO_ERROR (%d), got %d\n", + SAM_PWD_CHANGE_NO_ERROR, reject->extendedFailureReason); + return false; + } + + } else if ((dominfo->min_password_length > 0) && + (strlen(newpass) < dominfo->min_password_length)) { + + if (reject->extendedFailureReason != SAM_PWD_CHANGE_PASSWORD_TOO_SHORT) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_PASSWORD_TOO_SHORT (%d), got %d\n", + SAM_PWD_CHANGE_PASSWORD_TOO_SHORT, reject->extendedFailureReason); + return false; + } + + } else if ((dominfo->password_history_length > 0) && + strequal(oldpass, newpass)) { + + if (reject->extendedFailureReason != SAM_PWD_CHANGE_PWD_IN_HISTORY) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_PWD_IN_HISTORY (%d), got %d\n", + SAM_PWD_CHANGE_PWD_IN_HISTORY, reject->extendedFailureReason); + return false; + } + } else if (dominfo->password_properties & DOMAIN_PASSWORD_COMPLEX) { + + if (reject->extendedFailureReason != SAM_PWD_CHANGE_NOT_COMPLEX) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NOT_COMPLEX (%d), got %d\n", + SAM_PWD_CHANGE_NOT_COMPLEX, reject->extendedFailureReason); + return false; + } + + } + + if (reject->extendedFailureReason == SAM_PWD_CHANGE_PASSWORD_TOO_SHORT) { + /* retry with adjusted size */ + return test_ChangePasswordUser3(p, tctx, account_string, + dominfo->min_password_length, + password, NULL, 0, false); + + } + + } else if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + if (reject && reject->extendedFailureReason != SAM_PWD_CHANGE_NO_ERROR) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NO_ERROR (%d), got %d\n", + SAM_PWD_CHANGE_NO_ERROR, reject->extendedFailureReason); + return false; + } + /* Perhaps the server has a 'min password age' set? */ + + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, "ChangePasswordUser3"); + + *password = talloc_strdup(tctx, newpass); + } + + return ret; +} + +bool test_ChangePasswordUser4(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *account_string, + int policy_min_pw_len, + char **password, + const char *newpassword) +{ +#ifdef HAVE_GNUTLS_PBKDF2 + struct dcerpc_binding_handle *b = p->binding_handle; + struct samr_ChangePasswordUser4 r; + const char *oldpassword = *password; + char *srv_str = NULL; + struct lsa_String server; + struct lsa_String account; + uint8_t old_nt_key_data[16] = {0}; + gnutls_datum_t old_nt_key = { + .data = old_nt_key_data, + .size = sizeof(old_nt_key), + }; + uint8_t cek_data[16] = {0}; + DATA_BLOB cek = { + .data = cek_data, + .length = sizeof(cek_data), + }; + uint8_t pw_data[514] = {0}; + DATA_BLOB plaintext = { + .data = pw_data, + .length = sizeof(pw_data), + }; + DATA_BLOB ciphertext = data_blob_null; + struct samr_EncryptedPasswordAES pwd_buf = {.cipher_len = 0}; + DATA_BLOB iv = { + .data = pwd_buf.salt, + .length = sizeof(pwd_buf.salt), + }; + gnutls_datum_t iv_datum = { + .data = iv.data, + .size = iv.length, + }; + uint64_t pbkdf2_iterations = generate_random_u64_range(5000, 1000000); + NTSTATUS status; + bool ok; + int rc; + + torture_comment(tctx, "Testing ChangePasswordUser4\n"); + + if (newpassword == NULL) { + do { + if (policy_min_pw_len == 0) { + newpassword = + samr_rand_pass(tctx, policy_min_pw_len); + } else { + newpassword = samr_rand_pass_fixed_len( + tctx, + policy_min_pw_len); + } + } while (check_password_quality(newpassword) == false); + } else { + torture_comment(tctx, "Using password '%s'\n", newpassword); + } + + torture_assert_not_null(tctx, + *password, + "Failing ChangePasswordUser4 as old password " + "was NULL. Previous test failed?"); + + srv_str = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + torture_assert_not_null(tctx, srv_str, "srvstr is NULL"); + init_lsa_String(&server, srv_str); + + init_lsa_String(&account, account_string); + + E_md4hash(oldpassword, old_nt_key_data); + + generate_nonce_buffer(iv.data, iv.length); + + rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512, + &old_nt_key, + &iv_datum, + pbkdf2_iterations, + cek.data, + cek.length); + torture_assert_int_equal(tctx, rc, 0, "gnutls_pbkdf2 failed"); + + ok = encode_pwd_buffer514_from_str(pw_data, newpassword, STR_UNICODE); + torture_assert(tctx, ok, "encode_aes_pw_buffer failed"); + + status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_encrypt( + tctx, + &plaintext, + &cek, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + &ciphertext, + pwd_buf.auth_data); + torture_assert_ntstatus_ok( + tctx, + status, + "samba_gnutls_aead_aes_256_cbc_hmac_sha512_encrypt failed"); + + pwd_buf.cipher_len = ciphertext.length; + pwd_buf.cipher = ciphertext.data; + pwd_buf.PBKDF2Iterations = pbkdf2_iterations; + + r.in.server = &server; + r.in.account = &account; + r.in.password = &pwd_buf; + + status = dcerpc_samr_ChangePasswordUser4_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ChangePasswordUser4 failed"); + + *password = talloc_strdup(tctx, newpassword); +#endif /* HAVE_GNUTLS_PBKDF2 */ + return true; +} + +bool test_ChangePasswordRandomBytes(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *account_string, + struct policy_handle *handle, + char **password) +{ + NTSTATUS status; + struct samr_ChangePasswordUser3 r; + struct samr_SetUserInfo s; + union samr_UserInfo u; + DATA_BLOB session_key; + + bool ret = true; + struct lsa_String server, account; + struct samr_CryptPassword nt_pass; + struct samr_Password nt_verifier; + DATA_BLOB new_random_pass; + char *newpass; + char *oldpass; + struct dcerpc_binding_handle *b = p->binding_handle; + uint8_t old_nt_hash[16] = { 0 }, new_nt_hash[16]; + DATA_BLOB old_nt_hash_blob + = data_blob_const(old_nt_hash, + sizeof(old_nt_hash)); + NTTIME t; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + gnutls_cipher_hd_t cipher_hnd = NULL; + uint8_t _confounder[16] = {0}; + DATA_BLOB confounder + = data_blob_const(_confounder, + sizeof(_confounder)); + DATA_BLOB pw_data; + gnutls_datum_t old_nt_key = { + .data = old_nt_hash, + .size = sizeof(old_nt_hash), + }; + + new_random_pass = samr_very_rand_pass(tctx, 128); + + torture_assert(tctx, *password != NULL, + "Failing ChangePasswordUser3 as old password was NULL. Previous test failed?"); + + oldpass = *password; + server.string = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + init_lsa_String(&account, account_string); + + s.in.user_handle = handle; + s.in.info = &u; + s.in.level = 25; + + ZERO_STRUCT(u); + + u.info25.info.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT; + + set_pw_in_buffer(u.info25.password.data, &new_random_pass); + + pw_data = data_blob_const(u.info25.password.data, 516); + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + return false; + } + + generate_random_buffer(_confounder, + sizeof(_confounder)); + + samba_gnutls_arcfour_confounded_md5(&confounder, + &session_key, + &pw_data, + SAMBA_GNUTLS_ENCRYPT); + + memcpy(&u.info25.password.data[516], _confounder, sizeof(_confounder)); + + torture_comment(tctx, "Testing SetUserInfo level 25 (set password ex) with a password made up of only random bytes\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &s), + "SetUserInfo failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, "RANDOM", nt_errstr(s.out.result)); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetUserInfo level %u failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + ret = false; + } + + torture_comment(tctx, "Testing ChangePasswordUser3 with a password made up of only random bytes\n"); + + mdfour(old_nt_hash, new_random_pass.data, new_random_pass.length); + + new_random_pass = samr_very_rand_pass(tctx, 128); + + mdfour(new_nt_hash, new_random_pass.data, new_random_pass.length); + + set_pw_in_buffer(nt_pass.data, &new_random_pass); + + gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &old_nt_key, + NULL); + gnutls_cipher_encrypt(cipher_hnd, + nt_pass.data, + 516); + gnutls_cipher_deinit(cipher_hnd); + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 0; + r.in.lm_password = NULL; + r.in.lm_verifier = NULL; + r.in.password3 = NULL; + r.out.dominfo = &dominfo; + r.out.reject = &reject; + + unix_to_nt_time(&t, time(NULL)); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, "RANDOM", nt_errstr(r.out.result)); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + if (reject && reject->extendedFailureReason != SAM_PWD_CHANGE_NO_ERROR) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NO_ERROR (%d), got %d\n", + SAM_PWD_CHANGE_NO_ERROR, reject->extendedFailureReason); + return false; + } + /* Perhaps the server has a 'min password age' set? */ + + } else if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "ChangePasswordUser3 failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + newpass = samr_rand_pass(tctx, 128); + + mdfour(old_nt_hash, new_random_pass.data, new_random_pass.length); + + E_md4hash(newpass, new_nt_hash); + + status = init_samr_CryptPassword(newpass, + &old_nt_hash_blob, + &nt_pass); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + E_old_pw_hash(new_nt_hash, old_nt_hash, nt_verifier.hash); + + r.in.server = &server; + r.in.account = &account; + r.in.nt_password = &nt_pass; + r.in.nt_verifier = &nt_verifier; + r.in.lm_change = 0; + r.in.lm_password = NULL; + r.in.lm_verifier = NULL; + r.in.password3 = NULL; + r.out.dominfo = &dominfo; + r.out.reject = &reject; + + unix_to_nt_time(&t, time(NULL)); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_ChangePasswordUser3_r(b, tctx, &r), + "ChangePasswordUser3 failed"); + torture_comment(tctx, "(%s:%s) old_password[%s] new_password[%s] status[%s]\n", + __location__, __FUNCTION__, + oldpass, newpass, nt_errstr(r.out.result)); + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_PASSWORD_RESTRICTION)) { + if (reject && reject->extendedFailureReason != SAM_PWD_CHANGE_NO_ERROR) { + torture_result(tctx, TORTURE_FAIL, "expected SAM_PWD_CHANGE_NO_ERROR (%d), got %d\n", + SAM_PWD_CHANGE_NO_ERROR, reject->extendedFailureReason); + return false; + } + /* Perhaps the server has a 'min password age' set? */ + + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, "ChangePasswordUser3 (on second random password)"); + *password = talloc_strdup(tctx, newpass); + } + + return ret; +} + + +static bool test_GetMembersInAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *alias_handle) +{ + struct samr_GetMembersInAlias r; + struct lsa_SidArray sids; + + torture_comment(tctx, "Testing GetMembersInAlias\n"); + + r.in.alias_handle = alias_handle; + r.out.sids = &sids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetMembersInAlias_r(b, tctx, &r), + "GetMembersInAlias failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GetMembersInAlias failed"); + + return true; +} + +static bool test_AddMemberToAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *alias_handle, + const struct dom_sid *domain_sid) +{ + struct samr_AddAliasMember r; + struct samr_DeleteAliasMember d; + struct dom_sid *sid; + + sid = dom_sid_add_rid(tctx, domain_sid, 512); + + torture_comment(tctx, "Testing AddAliasMember\n"); + r.in.alias_handle = alias_handle; + r.in.sid = sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_AddAliasMember_r(b, tctx, &r), + "AddAliasMember failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "AddAliasMember failed"); + + d.in.alias_handle = alias_handle; + d.in.sid = sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteAliasMember_r(b, tctx, &d), + "DeleteAliasMember failed"); + torture_assert_ntstatus_ok(tctx, d.out.result, "DelAliasMember failed"); + + return true; +} + +static bool test_AddMultipleMembersToAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *alias_handle) +{ + struct samr_AddMultipleMembersToAlias a; + struct samr_RemoveMultipleMembersFromAlias r; + struct lsa_SidArray sids; + + torture_comment(tctx, "Testing AddMultipleMembersToAlias\n"); + a.in.alias_handle = alias_handle; + a.in.sids = &sids; + + sids.num_sids = 3; + sids.sids = talloc_array(tctx, struct lsa_SidPtr, 3); + + sids.sids[0].sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1-2-3-1"); + sids.sids[1].sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1-2-3-2"); + sids.sids[2].sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1-2-3-3"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_AddMultipleMembersToAlias_r(b, tctx, &a), + "AddMultipleMembersToAlias failed"); + torture_assert_ntstatus_ok(tctx, a.out.result, "AddMultipleMembersToAlias"); + + + torture_comment(tctx, "Testing RemoveMultipleMembersFromAlias\n"); + r.in.alias_handle = alias_handle; + r.in.sids = &sids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_RemoveMultipleMembersFromAlias_r(b, tctx, &r), + "RemoveMultipleMembersFromAlias failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "RemoveMultipleMembersFromAlias failed"); + + /* strange! removing twice doesn't give any error */ + torture_assert_ntstatus_ok(tctx, dcerpc_samr_RemoveMultipleMembersFromAlias_r(b, tctx, &r), + "RemoveMultipleMembersFromAlias failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "RemoveMultipleMembersFromAlias failed"); + + /* but removing an alias that isn't there does */ + sids.sids[2].sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1-2-3-4"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_RemoveMultipleMembersFromAlias_r(b, tctx, &r), + "RemoveMultipleMembersFromAlias failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND, "RemoveMultipleMembersFromAlias"); + + return true; +} + +static bool test_GetAliasMembership(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle) +{ + struct samr_GetAliasMembership r; + struct lsa_SidArray sids; + struct samr_Ids rids; + + torture_comment(tctx, "Testing GetAliasMembership\n"); + + r.in.domain_handle = domain_handle; + r.in.sids = &sids; + r.out.rids = &rids; + + sids.num_sids = 0; + sids.sids = talloc_zero_array(tctx, struct lsa_SidPtr, sids.num_sids); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetAliasMembership_r(b, tctx, &r), + "GetAliasMembership failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_GetAliasMembership failed"); + + torture_assert_int_equal(tctx, sids.num_sids, rids.count, + "protocol misbehaviour"); + + sids.num_sids = 1; + sids.sids = talloc_zero_array(tctx, struct lsa_SidPtr, sids.num_sids); + sids.sids[0].sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1-2-3-1"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetAliasMembership_r(b, tctx, &r), + "samr_GetAliasMembership failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_GetAliasMembership failed"); + +#if 0 + /* only true for w2k8 it seems + * win7, xp, w2k3 will return a 0 length array pointer */ + + if (rids.ids && (rids.count == 0)) { + torture_fail(tctx, "samr_GetAliasMembership returned 0 count and a rids array"); + } +#endif + if (!rids.ids && rids.count) { + torture_fail(tctx, "samr_GetAliasMembership returned non-0 count but no rids"); + } + + return true; +} + +static bool test_TestPrivateFunctionsUser(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *user_handle) +{ + struct samr_TestPrivateFunctionsUser r; + + torture_comment(tctx, "Testing TestPrivateFunctionsUser\n"); + + r.in.user_handle = user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_TestPrivateFunctionsUser_r(b, tctx, &r), + "TestPrivateFunctionsUser failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_IMPLEMENTED, "TestPrivateFunctionsUser"); + + return true; +} + +static bool test_QueryUserInfo_pwdlastset(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + bool use_info2, + NTTIME *pwdlastset) +{ + NTSTATUS status; + uint16_t levels[] = { /* 3, */ 5, 21 }; + int i; + /* NTTIME pwdlastset3 = 0; */ + NTTIME pwdlastset5 = 0; + NTTIME pwdlastset21 = 0; + + torture_comment(tctx, "Testing QueryUserInfo%s level 5 and 21 call ", + use_info2 ? "2":""); + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + struct samr_QueryUserInfo r; + struct samr_QueryUserInfo2 r2; + union samr_UserInfo *info; + + if (use_info2) { + r2.in.user_handle = handle; + r2.in.level = levels[i]; + r2.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo2_r(b, tctx, &r2), + "QueryUserInfo2 failed"); + status = r2.out.result; + + } else { + r.in.user_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "QueryUserInfo failed"); + status = r.out.result; + } + + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_INVALID_INFO_CLASS)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo%s level %u failed - %s\n", + use_info2 ? "2":"", levels[i], nt_errstr(status)); + return false; + } + + switch (levels[i]) { + case 3: + /* pwdlastset3 = info->info3.last_password_change; */ + break; + case 5: + pwdlastset5 = info->info5.last_password_change; + break; + case 21: + pwdlastset21 = info->info21.last_password_change; + break; + default: + return false; + } + } + /* torture_assert_int_equal(tctx, pwdlastset3, pwdlastset5, + "pwdlastset mixup"); */ + torture_assert_int_equal(tctx, pwdlastset5, pwdlastset21, + "pwdlastset mixup"); + + *pwdlastset = pwdlastset21; + + torture_comment(tctx, "(pwdlastset: %llu)\n", + (unsigned long long) *pwdlastset); + + return true; +} + +static bool test_SamLogon(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials, + struct cli_credentials *test_credentials, + NTSTATUS expected_result, + bool interactive) +{ + NTSTATUS status; + struct netr_LogonSamLogonEx r; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative; + struct netr_IdentityInfo identity; + struct netr_NetworkInfo ninfo; + struct netr_PasswordInfo pinfo; + DATA_BLOB names_blob, chal, lm_resp, nt_resp; + int flags = CLI_CRED_NTLM_AUTH; + uint32_t samlogon_flags = 0; + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator a; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, (creds = cli_credentials_get_netlogon_creds(machine_credentials)), ""); + + if (lpcfg_client_lanman_auth(tctx->lp_ctx)) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (lpcfg_client_ntlmv2_auth(tctx->lp_ctx)) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + + cli_credentials_get_ntlm_username_domain(test_credentials, tctx, + &identity.account_name.string, + &identity.domain_name.string); + + identity.parameter_control = + MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | + MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT; + identity.logon_id = 0; + identity.workstation.string = cli_credentials_get_workstation(test_credentials); + + if (interactive) { + netlogon_creds_client_authenticator(creds, &a); + + if (!E_deshash(cli_credentials_get_password(test_credentials), pinfo.lmpassword.hash)) { + ZERO_STRUCT(pinfo.lmpassword.hash); + } + E_md4hash(cli_credentials_get_password(test_credentials), pinfo.ntpassword.hash); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_encrypt(creds, pinfo.lmpassword.hash, 16); + netlogon_creds_aes_encrypt(creds, pinfo.ntpassword.hash, 16); + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + netlogon_creds_arcfour_crypt(creds, pinfo.lmpassword.hash, 16); + netlogon_creds_arcfour_crypt(creds, pinfo.ntpassword.hash, 16); + } else { + netlogon_creds_des_encrypt(creds, &pinfo.lmpassword); + netlogon_creds_des_encrypt(creds, &pinfo.ntpassword); + } + + pinfo.identity_info = identity; + logon.password = &pinfo; + + r.in.logon_level = NetlogonInteractiveInformation; + } else { + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + chal = data_blob_const(ninfo.challenge, + sizeof(ninfo.challenge)); + + names_blob = NTLMv2_generate_names_blob(tctx, cli_credentials_get_workstation(test_credentials), + cli_credentials_get_domain(test_credentials)); + + status = cli_credentials_get_ntlm_response(test_credentials, tctx, + &flags, + chal, + NULL, /* server_timestamp */ + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(tctx, status, "cli_credentials_get_ntlm_response failed"); + + ninfo.lm.data = lm_resp.data; + ninfo.lm.length = lm_resp.length; + + ninfo.nt.data = nt_resp.data; + ninfo.nt.length = nt_resp.length; + + ninfo.identity_info = identity; + logon.network = &ninfo; + + r.in.logon_level = NetlogonNetworkInformation; + } + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(test_credentials); + r.in.logon = &logon; + r.in.flags = &samlogon_flags; + r.out.flags = &samlogon_flags; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + + torture_comment(tctx, "Testing LogonSamLogon with name %s\n", identity.account_name.string); + + r.in.validation_level = 6; + + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "netr_LogonSamLogonEx failed"); + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_INFO_CLASS)) { + r.in.validation_level = 3; + torture_assert_ntstatus_ok(tctx, dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "netr_LogonSamLogonEx failed"); + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_assert_ntstatus_equal(tctx, r.out.result, expected_result, "LogonSamLogonEx failed"); + return true; + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, "LogonSamLogonEx failed"); + } + + return true; +} + +static bool test_SamLogon_with_creds(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_creds, + const char *acct_name, + const char *password, + NTSTATUS expected_samlogon_result, + bool interactive) +{ + bool ret = true; + struct cli_credentials *test_credentials; + + test_credentials = cli_credentials_init(tctx); + + cli_credentials_set_workstation(test_credentials, + cli_credentials_get_workstation(machine_creds), CRED_SPECIFIED); + cli_credentials_set_domain(test_credentials, + cli_credentials_get_domain(machine_creds), CRED_SPECIFIED); + cli_credentials_set_username(test_credentials, + acct_name, CRED_SPECIFIED); + cli_credentials_set_password(test_credentials, + password, CRED_SPECIFIED); + + torture_comment(tctx, "Testing samlogon (%s) as %s password: %s\n", + interactive ? "interactive" : "network", acct_name, password); + + if (!test_SamLogon(tctx, p, machine_creds, test_credentials, + expected_samlogon_result, interactive)) { + torture_result(tctx, TORTURE_FAIL, "new password did not work\n"); + ret = false; + } + + return ret; +} + +static bool test_SetPassword_level(struct dcerpc_pipe *p, + struct dcerpc_pipe *np, + struct torture_context *tctx, + struct policy_handle *handle, + uint16_t level, + uint32_t fields_present, + uint8_t password_expired, + bool *matched_expected_error, + bool use_setinfo2, + const char *acct_name, + char **password, + struct cli_credentials *machine_creds, + bool use_queryinfo2, + NTTIME *pwdlastset, + NTSTATUS expected_samlogon_result) +{ + const char *fields = NULL; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + switch (level) { + case 21: + case 23: + case 25: + case 32: + fields = talloc_asprintf(tctx, "(fields_present: 0x%08x)", + fields_present); + break; + default: + break; + } + + torture_comment(tctx, "Testing SetUserInfo%s level %d call " + "(password_expired: %d) %s\n", + use_setinfo2 ? "2":"", level, password_expired, + fields ? fields : ""); + + if (!test_SetUserPass_level_ex(p, tctx, handle, level, + fields_present, + password, + password_expired, + use_setinfo2, + matched_expected_error)) { + ret = false; + } + + if (!test_QueryUserInfo_pwdlastset(b, tctx, handle, + use_queryinfo2, + pwdlastset)) { + ret = false; + } + + if (*matched_expected_error == true) { + return ret; + } + + if (!test_SamLogon_with_creds(tctx, np, + machine_creds, + acct_name, + *password, + expected_samlogon_result, + false)) { + ret = false; + } + + return ret; +} + +static bool setup_schannel_netlogon_pipe(struct torture_context *tctx, + struct cli_credentials *credentials, + struct dcerpc_pipe **p) +{ + struct dcerpc_binding *b; + NTSTATUS status; + + torture_assert_ntstatus_ok(tctx, torture_rpc_binding(tctx, &b), + "failed to get rpc binding"); + + /* We have to use schannel, otherwise the SamLogonEx fails + * with INTERNAL_ERROR */ + + status = dcerpc_binding_set_flags(b, + DCERPC_SCHANNEL | + DCERPC_SIGN | DCERPC_SEAL | + DCERPC_SCHANNEL_AUTO, + DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, p, b, &ndr_table_netlogon, + credentials, tctx->ev, tctx->lp_ctx), + "failed to bind to netlogon"); + + return true; +} + +static bool test_SetPassword_pwdlastset(struct dcerpc_pipe *p, + struct torture_context *tctx, + uint32_t acct_flags, + const char *acct_name, + struct policy_handle *handle, + char **password, + struct cli_credentials *machine_credentials) +{ + int s = 0, q = 0, f = 0, l = 0, z = 0; + bool ret = true; + int delay = 50000; + bool set_levels[] = { false, true }; + bool query_levels[] = { false, true }; + uint32_t levels[] = { 18, 21, 26, 23, 24, 25, 31 }; /* Second half only used when TEST_ALL_LEVELS defined */ + uint32_t nonzeros[] = { 1, 24 }; + uint32_t fields_present[] = { + 0, + SAMR_FIELD_EXPIRED_FLAG, + SAMR_FIELD_LAST_PWD_CHANGE, + SAMR_FIELD_EXPIRED_FLAG | SAMR_FIELD_LAST_PWD_CHANGE, + SAMR_FIELD_COMMENT, + SAMR_FIELD_NT_PASSWORD_PRESENT, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_EXPIRED_FLAG, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_EXPIRED_FLAG, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT | SAMR_FIELD_LAST_PWD_CHANGE | SAMR_FIELD_EXPIRED_FLAG + }; + struct dcerpc_pipe *np = NULL; + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { + delay = 999999; + torture_comment(tctx, "Samba3 has second granularity, setting delay to: %d\n", + delay); + } + + torture_assert(tctx, setup_schannel_netlogon_pipe(tctx, machine_credentials, &np), ""); + + /* set to 1 to enable testing for all possible opcode + (SetUserInfo, SetUserInfo2, QueryUserInfo, QueryUserInfo2) + combinations */ +#if 0 +#define TEST_ALL_LEVELS 1 +#define TEST_SET_LEVELS 1 +#define TEST_QUERY_LEVELS 1 +#endif +#ifdef TEST_ALL_LEVELS + for (l=0; l<ARRAY_SIZE(levels); l++) { +#else + for (l=0; l<(ARRAY_SIZE(levels))/2; l++) { +#endif + for (z=0; z<ARRAY_SIZE(nonzeros); z++) { + for (f=0; f<ARRAY_SIZE(fields_present); f++) { +#ifdef TEST_SET_LEVELS + for (s=0; s<ARRAY_SIZE(set_levels); s++) { +#endif +#ifdef TEST_QUERY_LEVELS + for (q=0; q<ARRAY_SIZE(query_levels); q++) { +#endif + NTTIME pwdlastset_old = 0; + NTTIME pwdlastset_new = 0; + bool matched_expected_error = false; + NTSTATUS expected_samlogon_result = NT_STATUS_ACCOUNT_DISABLED; + + torture_comment(tctx, "------------------------------\n" + "Testing pwdLastSet attribute for flags: 0x%08x " + "(s: %d (l: %d), q: %d)\n", + acct_flags, s, levels[l], q); + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if (!((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT))) { + expected_samlogon_result = NT_STATUS_WRONG_PASSWORD; + } + break; + } + + + /* set #1 */ + + /* set a password and force password change (pwdlastset 0) by + * setting the password expired flag to a non-0 value */ + + if (!test_SetPassword_level(p, np, tctx, handle, + levels[l], + fields_present[f], + nonzeros[z], + &matched_expected_error, + set_levels[s], + acct_name, + password, + machine_credentials, + query_levels[q], + &pwdlastset_new, + expected_samlogon_result)) { + ret = false; + } + + if (matched_expected_error == true) { + /* skipping on expected failure */ + continue; + } + + /* pwdlastset must be 0 afterwards, except for a level 21, 23 and 25 + * set without the SAMR_FIELD_EXPIRED_FLAG */ + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if ((pwdlastset_new != 0) && + !(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG)) { + torture_comment(tctx, "not considering a non-0 " + "pwdLastSet as a an error as the " + "SAMR_FIELD_EXPIRED_FLAG has not " + "been set\n"); + break; + } + break; + default: + if (pwdlastset_new != 0) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected pwdLastSet 0 but got %llu\n", + (unsigned long long) pwdlastset_old); + ret = false; + } + break; + } + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) && + (pwdlastset_old > 0) && (pwdlastset_new > 0) && + (pwdlastset_old >= pwdlastset_new)) { + torture_result(tctx, TORTURE_FAIL, "pwdlastset not increasing\n"); + ret = false; + } + break; + } + + pwdlastset_old = pwdlastset_new; + + usleep(delay); + + /* set #2 */ + + /* set a password, pwdlastset needs to get updated (increased + * value), password_expired value used here is 0 */ + + if (!test_SetPassword_level(p, np, tctx, handle, + levels[l], + fields_present[f], + 0, + &matched_expected_error, + set_levels[s], + acct_name, + password, + machine_credentials, + query_levels[q], + &pwdlastset_new, + expected_samlogon_result)) { + ret = false; + } + + /* when a password has been changed, pwdlastset must not be 0 afterwards + * and must be larger then the old value */ + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + /* SAMR_FIELD_EXPIRED_FLAG has not been set and no + * password has been changed, old and new pwdlastset + * need to be the same value */ + + if (!(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG) && + !((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT))) + { + torture_assert_int_equal(tctx, pwdlastset_old, + pwdlastset_new, "pwdlastset must be equal"); + break; + } + break; + default: + if (pwdlastset_old >= pwdlastset_new) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected last pwdlastset (%llu) < new pwdlastset (%llu)\n", + (unsigned long long) pwdlastset_old, + (unsigned long long) pwdlastset_new); + ret = false; + } + if (pwdlastset_new == 0) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected non-0 pwdlastset, got: %llu\n", + (unsigned long long) pwdlastset_new); + ret = false; + } + break; + } + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) && + (pwdlastset_old > 0) && (pwdlastset_new > 0) && + (pwdlastset_old >= pwdlastset_new)) { + torture_result(tctx, TORTURE_FAIL, "pwdlastset not increasing\n"); + ret = false; + } + break; + } + + pwdlastset_old = pwdlastset_new; + + usleep(delay); + + /* set #2b */ + + /* set a password, pwdlastset needs to get updated (increased + * value), password_expired value used here is 0 */ + + if (!test_SetPassword_level(p, np, tctx, handle, + levels[l], + fields_present[f], + 0, + &matched_expected_error, + set_levels[s], + acct_name, + password, + machine_credentials, + query_levels[q], + &pwdlastset_new, + expected_samlogon_result)) { + ret = false; + } + + /* when a password has been changed, pwdlastset must not be 0 afterwards + * and must be larger then the old value */ + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + /* SAMR_FIELD_EXPIRED_FLAG has not been set and no + * password has been changed, old and new pwdlastset + * need to be the same value */ + + if (!(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG) && + !((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT))) + { + torture_assert_int_equal(tctx, pwdlastset_old, + pwdlastset_new, "pwdlastset must be equal"); + break; + } + break; + default: + if (pwdlastset_old >= pwdlastset_new) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected last pwdlastset (%llu) < new pwdlastset (%llu)\n", + (unsigned long long) pwdlastset_old, + (unsigned long long) pwdlastset_new); + ret = false; + } + if (pwdlastset_new == 0) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected non-0 pwdlastset, got: %llu\n", + (unsigned long long) pwdlastset_new); + ret = false; + } + break; + } + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) && + (pwdlastset_old > 0) && (pwdlastset_new > 0) && + (pwdlastset_old >= pwdlastset_new)) { + torture_result(tctx, TORTURE_FAIL, "pwdlastset not increasing\n"); + ret = false; + } + break; + } + + pwdlastset_old = pwdlastset_new; + + usleep(delay); + + /* set #3 */ + + /* set a password and force password change (pwdlastset 0) by + * setting the password expired flag to a non-0 value */ + + if (!test_SetPassword_level(p, np, tctx, handle, + levels[l], + fields_present[f], + nonzeros[z], + &matched_expected_error, + set_levels[s], + acct_name, + password, + machine_credentials, + query_levels[q], + &pwdlastset_new, + expected_samlogon_result)) { + ret = false; + } + + /* pwdlastset must be 0 afterwards, except for a level 21, 23 and 25 + * set without the SAMR_FIELD_EXPIRED_FLAG */ + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if ((pwdlastset_new != 0) && + !(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG)) { + torture_comment(tctx, "not considering a non-0 " + "pwdLastSet as a an error as the " + "SAMR_FIELD_EXPIRED_FLAG has not " + "been set\n"); + break; + } + + /* SAMR_FIELD_EXPIRED_FLAG has not been set and no + * password has been changed, old and new pwdlastset + * need to be the same value */ + + if (!(fields_present[f] & SAMR_FIELD_EXPIRED_FLAG) && + !((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT))) + { + torture_assert_int_equal(tctx, pwdlastset_old, + pwdlastset_new, "pwdlastset must be equal"); + break; + } + break; + default: + if (pwdlastset_new != 0) { + torture_result(tctx, TORTURE_FAIL, "pwdLastSet test failed: " + "expected pwdLastSet 0, got %llu\n", + (unsigned long long) pwdlastset_old); + ret = false; + } + break; + } + + switch (levels[l]) { + case 21: + case 23: + case 25: + case 32: + if (((fields_present[f] & SAMR_FIELD_NT_PASSWORD_PRESENT) || + (fields_present[f] & SAMR_FIELD_LM_PASSWORD_PRESENT)) && + (pwdlastset_old > 0) && (pwdlastset_new > 0) && + (pwdlastset_old >= pwdlastset_new)) { + torture_result(tctx, TORTURE_FAIL, "pwdlastset not increasing\n"); + ret = false; + } + break; + } + + /* if the level we are testing does not have a fields_present + * field, skip all fields present tests by setting f to to + * arraysize */ + switch (levels[l]) { + case 18: + case 24: + case 26: + case 31: + f = ARRAY_SIZE(fields_present); + break; + } + +#ifdef TEST_QUERY_LEVELS + } +#endif +#ifdef TEST_SET_LEVELS + } +#endif + } /* fields present */ + } /* nonzeros */ + } /* levels */ + +#undef TEST_SET_LEVELS +#undef TEST_QUERY_LEVELS + + talloc_free(np); + + return ret; +} + +static bool test_QueryUserInfo_badpwdcount(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t *badpwdcount) +{ + union samr_UserInfo *info; + struct samr_QueryUserInfo r; + + r.in.user_handle = handle; + r.in.level = 3; + r.out.info = &info; + + torture_comment(tctx, "Testing QueryUserInfo level %d", r.in.level); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "failed to query userinfo"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query userinfo"); + + *badpwdcount = info->info3.bad_password_count; + + torture_comment(tctx, " (bad password count: %d)\n", *badpwdcount); + + return true; +} + +static bool test_SetUserInfo_acct_flags(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *user_handle, + uint32_t acct_flags) +{ + struct samr_SetUserInfo r; + union samr_UserInfo user_info; + + torture_comment(tctx, "Testing SetUserInfo level 16\n"); + + user_info.info16.acct_flags = acct_flags; + + r.in.user_handle = user_handle; + r.in.level = 16; + r.in.info = &user_info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetUserInfo_r(b, tctx, &r), + "failed to set account flags"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to set account flags"); + + return true; +} + +static bool test_reset_badpwdcount(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *user_handle, + uint32_t acct_flags, + char **password) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, test_SetUserPass(p, tctx, user_handle, password), + "failed to set password"); + + torture_comment(tctx, "Testing SetUserInfo level 16 (enable account)\n"); + + torture_assert(tctx, + test_SetUserInfo_acct_flags(b, tctx, user_handle, + acct_flags & ~ACB_DISABLED), + "failed to enable user"); + + torture_assert(tctx, test_SetUserPass(p, tctx, user_handle, password), + "failed to set password"); + + return true; +} + +static bool test_SetDomainInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + enum samr_DomainInfoClass level, + union samr_DomainInfo *info) +{ + struct samr_SetDomainInfo r; + + r.in.domain_handle = domain_handle; + r.in.level = level; + r.in.info = info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_SetDomainInfo_r(b, tctx, &r), + "failed to set domain info"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to set domain info"); + + return true; +} + +static bool test_SetDomainInfo_ntstatus(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + enum samr_DomainInfoClass level, + union samr_DomainInfo *info, + NTSTATUS expected) +{ + struct samr_SetDomainInfo r; + + r.in.domain_handle = domain_handle; + r.in.level = level; + r.in.info = info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDomainInfo_r(b, tctx, &r), + "SetDomainInfo failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, expected, ""); + + return true; +} + +static bool test_QueryDomainInfo2_level(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + enum samr_DomainInfoClass level, + union samr_DomainInfo **q_info) +{ + struct samr_QueryDomainInfo2 r; + + r.in.domain_handle = domain_handle; + r.in.level = level; + r.out.info = q_info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo2_r(b, tctx, &r), + "failed to query domain info"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query domain info"); + + return true; +} + +static bool test_Password_badpwdcount(struct dcerpc_pipe *p, + struct dcerpc_pipe *np, + struct torture_context *tctx, + uint32_t acct_flags, + const char *acct_name, + struct policy_handle *domain_handle, + struct policy_handle *user_handle, + char **password, + struct cli_credentials *machine_credentials, + const char *comment, + bool disable, + bool interactive, + NTSTATUS expected_success_status, + struct samr_DomInfo1 *info1, + struct samr_DomInfo12 *info12) +{ + union samr_DomainInfo info; + char **passwords; + int i; + uint32_t badpwdcount, tmp; + uint32_t password_history_length = 12; + uint32_t lockout_threshold = 15; + uint32_t lockout_seconds = 5; + uint64_t delta_time_factor = 10 * 1000 * 1000; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + lockout_seconds = 60; + } + + torture_comment(tctx, "\nTesting bad pwd count with: %s\n", comment); + + torture_assert(tctx, password_history_length < lockout_threshold, + "password history length needs to be smaller than account lockout threshold for this test"); + + + /* set policies */ + + info.info1 = *info1; + info.info1.password_history_length = password_history_length; + info.info1.min_password_age = 0; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainPasswordInformation, &info), + "failed to set password history length and min passwd age"); + + info.info12 = *info12; + info.info12.lockout_threshold = lockout_threshold; + + /* set lockout duration of 5 seconds */ + info.info12.lockout_duration = ~(lockout_seconds * delta_time_factor); + info.info12.lockout_window = ~(lockout_seconds * delta_time_factor); + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &info), + "failed to set lockout threshold"); + + /* reset bad pwd count */ + + torture_assert(tctx, + test_reset_badpwdcount(p, tctx, user_handle, acct_flags, password), ""); + + + /* enable or disable account */ + if (disable) { + torture_assert(tctx, + test_SetUserInfo_acct_flags(b, tctx, user_handle, + acct_flags | ACB_DISABLED), + "failed to disable user"); + } else { + torture_assert(tctx, + test_SetUserInfo_acct_flags(b, tctx, user_handle, + acct_flags & ~ACB_DISABLED), + "failed to enable user"); + } + + + /* setup password history */ + + passwords = talloc_array(tctx, char *, password_history_length); + + for (i=0; i < password_history_length; i++) { + + torture_assert(tctx, test_SetUserPass(p, tctx, user_handle, password), + "failed to set password"); + passwords[i] = talloc_strdup(tctx, *password); + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, passwords[i], + expected_success_status, interactive)) { + torture_fail(tctx, "failed to auth with latest password"); + } + + torture_assert(tctx, + test_QueryUserInfo_badpwdcount(b, tctx, user_handle, &badpwdcount), ""); + + torture_assert_int_equal(tctx, badpwdcount, 0, "expected badpwdcount to be 0"); + } + + + /* test with wrong password */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, "random_crap", + NT_STATUS_WRONG_PASSWORD, interactive)) { + torture_fail(tctx, "succeeded to authenticate with wrong password"); + } + + torture_assert(tctx, + test_QueryUserInfo_badpwdcount(b, tctx, user_handle, &badpwdcount), ""); + + torture_assert_int_equal(tctx, badpwdcount, 1, "expected badpwdcount to be 1"); + + + /* test with latest good password */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, acct_name, + passwords[password_history_length-1], + expected_success_status, interactive)) { + torture_fail(tctx, "succeeded to authenticate with wrong password"); + } + + torture_assert(tctx, + test_QueryUserInfo_badpwdcount(b, tctx, user_handle, &badpwdcount), ""); + + if (disable) { + torture_assert_int_equal(tctx, badpwdcount, 1, "expected badpwdcount to be 1"); + } else { + /* only enabled accounts get the bad pwd count reset upon + * successful logon */ + torture_assert_int_equal(tctx, badpwdcount, 0, "expected badpwdcount to be 0"); + } + + tmp = badpwdcount; + + + /* test password history */ + + for (i=0; i < password_history_length; i++) { + + torture_comment(tctx, "Testing bad password count behavior with " + "password #%d of #%d\n", i, password_history_length); + + /* - network samlogon will succeed auth and not + * increase badpwdcount for 2 last entries + * - interactive samlogon only for the last one */ + + if (i == password_history_length - 1 || + (i == password_history_length - 2 && !interactive)) { + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, passwords[i], + expected_success_status, interactive)) { + torture_fail(tctx, talloc_asprintf(tctx, "did not successfully to obtain %s for %s login with old password (#%d of #%d in history)", + nt_errstr(expected_success_status), + interactive ? "interactive" : "network", i, password_history_length)); + } + + torture_assert(tctx, + test_QueryUserInfo_badpwdcount(b, tctx, user_handle, &badpwdcount), ""); + + if (disable) { + /* torture_comment(tctx, "expecting bad pwd count to *NOT INCREASE* for pwd history entry %d\n", i); */ + torture_assert_int_equal(tctx, badpwdcount, tmp, "unexpected badpwdcount"); + } else { + /* torture_comment(tctx, "expecting bad pwd count to be 0 for pwd history entry %d\n", i); */ + torture_assert_int_equal(tctx, badpwdcount, 0, "expected badpwdcount to be 0"); + } + + tmp = badpwdcount; + + continue; + } + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, passwords[i], + NT_STATUS_WRONG_PASSWORD, interactive)) { + torture_fail(tctx, talloc_asprintf(tctx, "succeeded to authenticate with old password (#%d of #%d in history)", i, password_history_length)); + } + + torture_assert(tctx, + test_QueryUserInfo_badpwdcount(b, tctx, user_handle, &badpwdcount), ""); + + /* - network samlogon will fail auth but not increase + * badpwdcount for 3rd last entry + * - interactive samlogon for 3rd and 2nd last entry */ + + if (i == password_history_length - 3 || + (i == password_history_length - 2 && interactive)) { + /* torture_comment(tctx, "expecting bad pwd count to *NOT INCREASE * by one for pwd history entry %d\n", i); */ + torture_assert_int_equal(tctx, badpwdcount, tmp, "unexpected badpwdcount"); + } else { + /* torture_comment(tctx, "expecting bad pwd count to increase by one for pwd history entry %d\n", i); */ + torture_assert_int_equal(tctx, badpwdcount, tmp + 1, "unexpected badpwdcount"); + } + + tmp = badpwdcount; + } + + return true; +} + +static bool test_Password_badpwdcount_wrap(struct dcerpc_pipe *p, + struct torture_context *tctx, + uint32_t acct_flags, + const char *acct_name, + struct policy_handle *domain_handle, + struct policy_handle *user_handle, + char **password, + struct cli_credentials *machine_credentials) +{ + union samr_DomainInfo *q_info, s_info; + struct samr_DomInfo1 info1, _info1; + struct samr_DomInfo12 info12, _info12; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *np; + int i; + + struct { + const char *comment; + bool disabled; + bool interactive; + NTSTATUS expected_success_status; + } creds[] = { + { + .comment = "network logon (disabled account)", + .disabled = true, + .interactive = false, + .expected_success_status= NT_STATUS_ACCOUNT_DISABLED + }, + { + .comment = "network logon (enabled account)", + .disabled = false, + .interactive = false, + .expected_success_status= NT_STATUS_OK + }, + { + .comment = "interactive logon (disabled account)", + .disabled = true, + .interactive = true, + .expected_success_status= NT_STATUS_ACCOUNT_DISABLED + }, + { + .comment = "interactive logon (enabled account)", + .disabled = false, + .interactive = true, + .expected_success_status= NT_STATUS_OK + }, + }; + + torture_assert(tctx, setup_schannel_netlogon_pipe(tctx, machine_credentials, &np), ""); + + /* backup old policies */ + + torture_assert(tctx, + test_QueryDomainInfo2_level(b, tctx, domain_handle, + DomainPasswordInformation, &q_info), + "failed to query domain info level 1"); + + info1 = q_info->info1; + _info1 = info1; + + torture_assert(tctx, + test_QueryDomainInfo2_level(b, tctx, domain_handle, + DomainLockoutInformation, &q_info), + "failed to query domain info level 12"); + + info12 = q_info->info12; + _info12 = info12; + + /* run tests */ + + for (i=0; i < ARRAY_SIZE(creds); i++) { + + /* skip trust tests for now */ + if (acct_flags & ACB_WSTRUST || + acct_flags & ACB_SVRTRUST || + acct_flags & ACB_DOMTRUST) { + continue; + } + + if (!test_Password_badpwdcount(p, np, tctx, acct_flags, acct_name, + domain_handle, user_handle, password, + machine_credentials, + creds[i].comment, + creds[i].disabled, + creds[i].interactive, + creds[i].expected_success_status, + &_info1, &_info12)) { + torture_result(tctx, TORTURE_FAIL, "TEST #%d (%s) failed\n", i, creds[i].comment); + ret = false; + } else { + torture_comment(tctx, "TEST #%d (%s) succeeded\n", i, creds[i].comment); + } + } + + /* restore policies */ + + s_info.info1 = info1; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainPasswordInformation, &s_info), + "failed to set password information"); + + s_info.info12 = info12; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &s_info), + "failed to set lockout information"); + + return ret; +} + +static bool test_QueryUserInfo_lockout(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *acct_name, + uint16_t raw_bad_password_count, + uint16_t effective_bad_password_count, + uint32_t effective_acb_lockout) +{ + struct policy_handle user_handle; + union samr_UserInfo *i; + struct samr_QueryUserInfo r; + + NTSTATUS status = test_OpenUser_byname(b, tctx, domain_handle, acct_name, &user_handle); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + r.in.user_handle = &user_handle; + r.in.level = 3; + r.out.info = &i; + torture_comment(tctx, "Testing QueryUserInfo level %d", r.in.level); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "failed to query userinfo"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query userinfo"); + torture_comment(tctx, " (acct_flags: 0x%08x) (raw_bad_pwd_count: %u)\n", + i->info3.acct_flags, i->info3.bad_password_count); + torture_assert_int_equal(tctx, i->info3.bad_password_count, + raw_bad_password_count, + "raw badpwdcount"); + torture_assert_int_equal(tctx, i->info3.acct_flags & ACB_AUTOLOCK, + effective_acb_lockout, + "effective acb_lockout"); + TALLOC_FREE(i); + + r.in.user_handle = &user_handle; + r.in.level = 5; + r.out.info = &i; + torture_comment(tctx, "Testing QueryUserInfo level %d", r.in.level); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "failed to query userinfo"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query userinfo"); + torture_comment(tctx, " (acct_flags: 0x%08x) (effective_bad_pwd_count: %u)\n", + i->info5.acct_flags, i->info5.bad_password_count); + torture_assert_int_equal(tctx, i->info5.bad_password_count, + effective_bad_password_count, + "effective badpwdcount"); + torture_assert_int_equal(tctx, i->info5.acct_flags & ACB_AUTOLOCK, + effective_acb_lockout, + "effective acb_lockout"); + TALLOC_FREE(i); + + r.in.user_handle = &user_handle; + r.in.level = 16; + r.out.info = &i; + torture_comment(tctx, "Testing QueryUserInfo level %d", r.in.level); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "failed to query userinfo"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query userinfo"); + torture_comment(tctx, " (acct_flags: 0x%08x)\n", + i->info16.acct_flags); + torture_assert_int_equal(tctx, i->info16.acct_flags & ACB_AUTOLOCK, + effective_acb_lockout, + "effective acb_lockout"); + TALLOC_FREE(i); + + r.in.user_handle = &user_handle; + r.in.level = 21; + r.out.info = &i; + torture_comment(tctx, "Testing QueryUserInfo level %d", r.in.level); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "failed to query userinfo"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query userinfo"); + torture_comment(tctx, " (acct_flags: 0x%08x) (effective_bad_pwd_count: %u)\n", + i->info21.acct_flags, i->info21.bad_password_count); + torture_assert_int_equal(tctx, i->info21.bad_password_count, + effective_bad_password_count, + "effective badpwdcount"); + torture_assert_int_equal(tctx, i->info21.acct_flags & ACB_AUTOLOCK, + effective_acb_lockout, + "effective acb_lockout"); + TALLOC_FREE(i); + + if (!test_samr_handle_Close(b, tctx, &user_handle)) { + return false; + } + + return true; +} + +static bool test_Password_lockout(struct dcerpc_pipe *p, + struct dcerpc_pipe *np, + struct torture_context *tctx, + uint32_t acct_flags, + const char *acct_name, + struct policy_handle *domain_handle, + struct policy_handle *user_handle, + char **password, + struct cli_credentials *machine_credentials, + const char *comment, + bool disable, + bool interactive, + uint32_t password_history_length, + NTSTATUS expected_success_status, + struct samr_DomInfo1 *info1, + struct samr_DomInfo12 *info12) +{ + union samr_DomainInfo info; + uint64_t lockout_threshold = 1; + uint32_t lockout_seconds = 5; + uint64_t delta_time_factor = 10 * 1000 * 1000; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + lockout_seconds = 60; + } + + torture_comment(tctx, "\nTesting account lockout: %s\n", comment); + + /* set policies */ + + info.info1 = *info1; + + torture_comment(tctx, "setting password history length to %d.\n", password_history_length); + info.info1.password_history_length = password_history_length; + + torture_comment(tctx, "setting min password again.\n"); + info.info1.min_password_age = 0; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainPasswordInformation, &info), + "failed to set password history length"); + + info.info12 = *info12; + info.info12.lockout_threshold = lockout_threshold; + + /* set lockout duration < lockout window: should fail */ + info.info12.lockout_duration = ~(lockout_seconds * delta_time_factor); + info.info12.lockout_window = ~((lockout_seconds + 1) * delta_time_factor); + + torture_assert(tctx, + test_SetDomainInfo_ntstatus(b, tctx, domain_handle, + DomainLockoutInformation, &info, + NT_STATUS_INVALID_PARAMETER), + "setting lockout duration < lockout window gave unexpected result"); + + info.info12.lockout_duration = 0; + info.info12.lockout_window = 0; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &info), + "failed to set lockout window and duration to 0"); + + + /* set lockout duration of 5 seconds */ + info.info12.lockout_duration = ~(lockout_seconds * delta_time_factor); + info.info12.lockout_window = ~(lockout_seconds * delta_time_factor); + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &info), + "failed to set lockout window and duration to 5 seconds"); + + /* reset bad pwd count */ + + torture_assert(tctx, + test_reset_badpwdcount(p, tctx, user_handle, acct_flags, password), ""); + + + /* enable or disable account */ + + if (disable) { + torture_assert(tctx, + test_SetUserInfo_acct_flags(b, tctx, user_handle, + acct_flags | ACB_DISABLED), + "failed to disable user"); + } else { + torture_assert(tctx, + test_SetUserInfo_acct_flags(b, tctx, user_handle, + acct_flags & ~ACB_DISABLED), + "failed to enable user"); + } + + + /* test logon with right password */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, *password, + expected_success_status, interactive)) { + torture_fail(tctx, "failed to auth with latest password"); + } + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 0, 0, 0), + "expected account to not be locked"); + + /* test with wrong password ==> lockout */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, "random_crap", + NT_STATUS_WRONG_PASSWORD, interactive)) { + torture_fail(tctx, "succeeded to authenticate with wrong password"); + } + + /* + * curiously, windows does _not_ return fresh values of + * effective bad_password_count and ACB_AUTOLOCK. + */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to not be locked"); + + /* test with good password */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, acct_name, + *password, + NT_STATUS_ACCOUNT_LOCKED_OUT, interactive)) + { + torture_fail(tctx, "authenticate did not return NT_STATUS_ACCOUNT_LOCKED_OUT"); + } + + /* bad pwd count should not get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, *password, + NT_STATUS_ACCOUNT_LOCKED_OUT), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count should not get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_ACCOUNT_LOCKED_OUT), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count should not get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + /* with bad password */ + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, + acct_name, "random_crap2", + NT_STATUS_ACCOUNT_LOCKED_OUT, interactive)) + { + torture_fail(tctx, "authenticate did not return NT_STATUS_ACCOUNT_LOCKED_OUT"); + } + + /* bad pwd count should not get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + /* let lockout duration expire ==> unlock */ + + torture_comment(tctx, "let lockout duration expire...\n"); + sleep(lockout_seconds + 1); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 0, 0), + "expected account to not be locked"); + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, acct_name, + *password, + expected_success_status, interactive)) + { + torture_fail(tctx, "failed to authenticate after lockout expired"); + } + + if (NT_STATUS_IS_OK(expected_success_status)) { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 0, 0, 0), + "expected account to not be locked"); + } else { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 0, 0), + "expected account to not be locked"); + } + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_WRONG_PASSWORD), + "got wrong status from ChangePasswordUser2"); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, *password, NT_STATUS_ACCOUNT_LOCKED_OUT), + "got wrong status from ChangePasswordUser2"); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_ACCOUNT_LOCKED_OUT), + "got wrong status from ChangePasswordUser2"); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, ACB_AUTOLOCK), + "expected account to be locked"); + + /* let lockout duration expire ==> unlock */ + + torture_comment(tctx, "let lockout duration expire...\n"); + sleep(lockout_seconds + 1); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 0, 0), + "expected account to not be locked"); + + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, acct_name, + *password, + expected_success_status, interactive)) + { + torture_fail(tctx, "failed to authenticate after lockout expired"); + } + + if (NT_STATUS_IS_OK(expected_success_status)) { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 0, 0, 0), + "expected account to not be locked"); + } else { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 0, 0), + "expected account to not be locked"); + } + + /* Testing ChangePasswordUser behaviour with 3 attempts */ + info.info12.lockout_threshold = 3; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &info), + "failed to set lockout threshold to 3"); + + if (NT_STATUS_IS_OK(expected_success_status)) { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 0, 0, 0), + "expected account to not be locked"); + } else { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 0, 0), + "expected account to not be locked"); + } + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_WRONG_PASSWORD), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count will get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 1, 1, 0), + "expected account to not be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_WRONG_PASSWORD), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count will get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 2, 2, 0), + "expected account to not be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, "random_crap", NT_STATUS_WRONG_PASSWORD), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count should get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 3, 3, ACB_AUTOLOCK), + "expected account to be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2_ntstatus(p, tctx, acct_name, *password, NT_STATUS_ACCOUNT_LOCKED_OUT), + "got wrong status from ChangePasswordUser2"); + + /* bad pwd count should not get updated */ + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 3, 3, ACB_AUTOLOCK), + "expected account to be locked"); + + /* let lockout duration expire ==> unlock */ + + torture_comment(tctx, "let lockout duration expire...\n"); + sleep(lockout_seconds + 1); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 3, 0, 0), + "expected account to not be locked"); + + torture_assert(tctx, + test_ChangePasswordUser2(p, tctx, acct_name, password, NULL, false), + "got wrong status from ChangePasswordUser2"); + + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 3, 0, 0), + "expected account to not be locked"); + + /* Used to reset the badPwdCount for the other tests */ + if (!test_SamLogon_with_creds(tctx, np, machine_credentials, acct_name, + *password, + expected_success_status, interactive)) + { + torture_fail(tctx, "failed to authenticate after lockout expired"); + } + + if (NT_STATUS_IS_OK(expected_success_status)) { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 0, 0, 0), + "expected account to not be locked"); + } else { + torture_assert(tctx, + test_QueryUserInfo_lockout(b, tctx, domain_handle, acct_name, + 3, 0, 0), + "expected account to not be locked"); + } + + return true; +} + +static bool test_Password_lockout_wrap(struct dcerpc_pipe *p, + struct torture_context *tctx, + uint32_t acct_flags, + const char *acct_name, + struct policy_handle *domain_handle, + struct policy_handle *user_handle, + char **password, + struct cli_credentials *machine_credentials) +{ + union samr_DomainInfo *q_info, s_info; + struct samr_DomInfo1 info1, _info1; + struct samr_DomInfo12 info12, _info12; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *np; + int i; + + struct { + const char *comment; + bool disabled; + bool interactive; + uint32_t password_history_length; + NTSTATUS expected_success_status; + } creds[] = { + { + .comment = "network logon (disabled account)", + .disabled = true, + .interactive = false, + .expected_success_status= NT_STATUS_ACCOUNT_DISABLED + }, + { + .comment = "network logon (enabled account)", + .disabled = false, + .interactive = false, + .expected_success_status= NT_STATUS_OK + }, + { + .comment = "network logon (enabled account, history len = 1)", + .disabled = false, + .interactive = false, + .expected_success_status= NT_STATUS_OK, + .password_history_length = 1 + }, + { + .comment = "interactive logon (disabled account)", + .disabled = true, + .interactive = true, + .expected_success_status= NT_STATUS_ACCOUNT_DISABLED + }, + { + .comment = "interactive logon (enabled account)", + .disabled = false, + .interactive = true, + .expected_success_status= NT_STATUS_OK + }, + { + .comment = "interactive logon (enabled account, history len = 1)", + .disabled = false, + .interactive = true, + .expected_success_status= NT_STATUS_OK, + .password_history_length = 1 + }, + }; + + torture_assert(tctx, setup_schannel_netlogon_pipe(tctx, machine_credentials, &np), ""); + + /* backup old policies */ + + torture_assert(tctx, + test_QueryDomainInfo2_level(b, tctx, domain_handle, + DomainPasswordInformation, &q_info), + "failed to query domain info level 1"); + + info1 = q_info->info1; + _info1 = info1; + + torture_assert(tctx, + test_QueryDomainInfo2_level(b, tctx, domain_handle, + DomainLockoutInformation, &q_info), + "failed to query domain info level 12"); + + info12 = q_info->info12; + _info12 = info12; + + /* run tests */ + + for (i=0; i < ARRAY_SIZE(creds); i++) { + bool test_passed; + /* skip trust tests for now */ + if (acct_flags & ACB_WSTRUST || + acct_flags & ACB_SVRTRUST || + acct_flags & ACB_DOMTRUST) { + continue; + } + + test_passed = test_Password_lockout(p, np, tctx, acct_flags, acct_name, + domain_handle, user_handle, password, + machine_credentials, + creds[i].comment, + creds[i].disabled, + creds[i].interactive, + creds[i].password_history_length, + creds[i].expected_success_status, + &_info1, &_info12); + ret &= test_passed; + if (!test_passed) { + torture_result(tctx, TORTURE_FAIL, "TEST #%d (%s) failed\n", i, creds[i].comment); + break; + } else { + torture_comment(tctx, "TEST #%d (%s) succeeded\n", i, creds[i].comment); + } + } + + /* restore policies */ + + s_info.info1 = info1; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainPasswordInformation, &s_info), + "failed to set password information"); + + s_info.info12 = info12; + + torture_assert(tctx, + test_SetDomainInfo(b, tctx, domain_handle, + DomainLockoutInformation, &s_info), + "failed to set lockout information"); + + return ret; +} + +static bool test_DeleteUser_with_privs(struct dcerpc_pipe *p, + struct dcerpc_pipe *lp, + struct torture_context *tctx, + struct policy_handle *domain_handle, + struct policy_handle *lsa_handle, + struct policy_handle *user_handle, + const struct dom_sid *domain_sid, + uint32_t rid, + struct cli_credentials *machine_credentials) +{ + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_binding_handle *lb = lp->binding_handle; + + struct policy_handle lsa_acct_handle; + struct dom_sid *user_sid; + + user_sid = dom_sid_add_rid(tctx, domain_sid, rid); + + { + struct lsa_EnumAccountRights r; + struct lsa_RightSet rights; + + torture_comment(tctx, "Testing LSA EnumAccountRights\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.out.rights = &rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountRights_r(lb, tctx, &r), + "lsa_EnumAccountRights failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND, + "Expected enum rights for account to fail"); + } + + { + struct lsa_RightSet rights; + struct lsa_StringLarge names[2]; + struct lsa_AddAccountRights r; + + torture_comment(tctx, "Testing LSA AddAccountRights\n"); + + init_lsa_StringLarge(&names[0], "SeMachineAccountPrivilege"); + init_lsa_StringLarge(&names[1], NULL); + + rights.count = 1; + rights.names = names; + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.in.rights = &rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_AddAccountRights_r(lb, tctx, &r), + "lsa_AddAccountRights failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to add privileges"); + } + + { + struct lsa_RightSet rights; + struct lsa_StringLarge names[2]; + struct lsa_AddAccountRights r; + + torture_comment(tctx, "Testing LSA AddAccountRights 1\n"); + + init_lsa_StringLarge(&names[0], "SeInteractiveLogonRight"); + init_lsa_StringLarge(&names[1], NULL); + + rights.count = 1; + rights.names = names; + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.in.rights = &rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_AddAccountRights_r(lb, tctx, &r), + "lsa_AddAccountRights 1 failed"); + + if (torture_setting_bool(tctx, "nt4_dc", false)) { + /* + * The NT4 DC doesn't implement Rights. + */ + torture_assert_ntstatus_equal(tctx, r.out.result, + NT_STATUS_NO_SUCH_PRIVILEGE, + "Add rights failed with incorrect error"); + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to add rights"); + + } + } + + + { + struct lsa_EnumAccounts r; + uint32_t resume_handle = 0; + struct lsa_SidArray lsa_sid_array; + int i; + bool found_sid = false; + + torture_comment(tctx, "Testing LSA EnumAccounts\n"); + + r.in.handle = lsa_handle; + r.in.num_entries = 0x1000; + r.in.resume_handle = &resume_handle; + r.out.sids = &lsa_sid_array; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccounts_r(lb, tctx, &r), + "lsa_EnumAccounts failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to enum accounts"); + + for (i=0; i < lsa_sid_array.num_sids; i++) { + if (dom_sid_equal(user_sid, lsa_sid_array.sids[i].sid)) { + found_sid = true; + } + } + + torture_assert(tctx, found_sid, + "failed to list privileged account"); + } + + { + struct lsa_EnumAccountRights r; + struct lsa_RightSet user_rights; + uint32_t expected_count = 2; + + if (torture_setting_bool(tctx, "nt4_dc", false)) { + /* + * NT4 DC doesn't store rights. + */ + expected_count = 1; + } + + torture_comment(tctx, "Testing LSA EnumAccountRights\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.out.rights = &user_rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountRights_r(lb, tctx, &r), + "lsa_EnumAccountRights failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to enum rights for account"); + + if (user_rights.count < expected_count) { + torture_result(tctx, TORTURE_FAIL, "failed to find newly added rights"); + return false; + } + } + + { + struct lsa_OpenAccount r; + + torture_comment(tctx, "Testing LSA OpenAccount\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.acct_handle = &lsa_acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenAccount_r(lb, tctx, &r), + "lsa_OpenAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to open lsa account"); + } + + { + struct lsa_GetSystemAccessAccount r; + uint32_t access_mask; + + torture_comment(tctx, "Testing LSA GetSystemAccessAccount\n"); + + r.in.handle = &lsa_acct_handle; + r.out.access_mask = &access_mask; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetSystemAccessAccount_r(lb, tctx, &r), + "lsa_GetSystemAccessAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to get lsa system access account"); + } + + { + struct lsa_Close r; + + torture_comment(tctx, "Testing LSA Close\n"); + + r.in.handle = &lsa_acct_handle; + r.out.handle = &lsa_acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_Close_r(lb, tctx, &r), + "lsa_Close failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to close lsa"); + } + + { + struct samr_DeleteUser r; + + torture_comment(tctx, "Testing SAMR DeleteUser\n"); + + r.in.user_handle = user_handle; + r.out.user_handle = user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteUser_r(b, tctx, &r), + "DeleteUser failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "DeleteUser failed"); + } + + { + struct lsa_EnumAccounts r; + uint32_t resume_handle = 0; + struct lsa_SidArray lsa_sid_array; + int i; + bool found_sid = false; + + torture_comment(tctx, "Testing LSA EnumAccounts\n"); + + r.in.handle = lsa_handle; + r.in.num_entries = 0x1000; + r.in.resume_handle = &resume_handle; + r.out.sids = &lsa_sid_array; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccounts_r(lb, tctx, &r), + "lsa_EnumAccounts failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to enum accounts"); + + for (i=0; i < lsa_sid_array.num_sids; i++) { + if (dom_sid_equal(user_sid, lsa_sid_array.sids[i].sid)) { + found_sid = true; + } + } + + torture_assert(tctx, found_sid, + "failed to list privileged account"); + } + + { + struct lsa_EnumAccountRights r; + struct lsa_RightSet user_rights; + + torture_comment(tctx, "Testing LSA EnumAccountRights\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.out.rights = &user_rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountRights_r(lb, tctx, &r), + "lsa_EnumAccountRights failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to enum rights for account"); + + if (user_rights.count < 1) { + torture_result(tctx, TORTURE_FAIL, "failed to find newly added rights"); + return false; + } + } + + { + struct lsa_OpenAccount r; + + torture_comment(tctx, "Testing LSA OpenAccount\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.acct_handle = &lsa_acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_OpenAccount_r(lb, tctx, &r), + "lsa_OpenAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to open lsa account"); + } + + { + struct lsa_GetSystemAccessAccount r; + uint32_t access_mask; + + torture_comment(tctx, "Testing LSA GetSystemAccessAccount\n"); + + r.in.handle = &lsa_acct_handle; + r.out.access_mask = &access_mask; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetSystemAccessAccount_r(lb, tctx, &r), + "lsa_GetSystemAccessAccount failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to get lsa system access account"); + } + + { + struct lsa_DeleteObject r; + + torture_comment(tctx, "Testing LSA DeleteObject\n"); + + r.in.handle = &lsa_acct_handle; + r.out.handle = &lsa_acct_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_DeleteObject_r(lb, tctx, &r), + "lsa_DeleteObject failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to delete object"); + } + + { + struct lsa_EnumAccounts r; + uint32_t resume_handle = 0; + struct lsa_SidArray lsa_sid_array; + int i; + bool found_sid = false; + + torture_comment(tctx, "Testing LSA EnumAccounts\n"); + + r.in.handle = lsa_handle; + r.in.num_entries = 0x1000; + r.in.resume_handle = &resume_handle; + r.out.sids = &lsa_sid_array; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccounts_r(lb, tctx, &r), + "lsa_EnumAccounts failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "Failed to enum accounts"); + + for (i=0; i < lsa_sid_array.num_sids; i++) { + if (dom_sid_equal(user_sid, lsa_sid_array.sids[i].sid)) { + found_sid = true; + } + } + + torture_assert(tctx, !found_sid, + "should not have listed privileged account"); + } + + { + struct lsa_EnumAccountRights r; + struct lsa_RightSet user_rights; + + torture_comment(tctx, "Testing LSA EnumAccountRights\n"); + + r.in.handle = lsa_handle; + r.in.sid = user_sid; + r.out.rights = &user_rights; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_EnumAccountRights_r(lb, tctx, &r), + "lsa_EnumAccountRights failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_OBJECT_NAME_NOT_FOUND, + "Failed to enum rights for account"); + } + + return ret; +} + +static bool test_user_ops(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *user_handle, + struct policy_handle *domain_handle, + const struct dom_sid *domain_sid, + uint32_t base_acct_flags, + const char *base_acct_name, enum torture_samr_choice which_ops, + struct cli_credentials *machine_credentials) +{ + char *password = NULL; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + bool ret = true; + int i; + uint32_t rid; + const uint32_t password_fields[] = { + SAMR_FIELD_NT_PASSWORD_PRESENT, + SAMR_FIELD_LM_PASSWORD_PRESENT, + SAMR_FIELD_NT_PASSWORD_PRESENT | SAMR_FIELD_LM_PASSWORD_PRESENT, + 0 + }; + + status = test_LookupName(b, tctx, domain_handle, base_acct_name, &rid); + if (!NT_STATUS_IS_OK(status)) { + ret = false; + } + + switch (which_ops) { + case TORTURE_SAMR_USER_ATTRIBUTES: + if (!test_QuerySecurity(b, tctx, user_handle)) { + ret = false; + } + + if (!test_QueryUserInfo(b, tctx, user_handle)) { + ret = false; + } + + if (!test_QueryUserInfo2(b, tctx, user_handle)) { + ret = false; + } + + if (!test_SetUserInfo(b, tctx, user_handle, base_acct_flags, + base_acct_name)) { + ret = false; + } + + if (!test_GetUserPwInfo(b, tctx, user_handle)) { + ret = false; + } + + if (!test_TestPrivateFunctionsUser(b, tctx, user_handle)) { + ret = false; + } + + if (!test_SetUserPass(p, tctx, user_handle, &password)) { + ret = false; + } + break; + case TORTURE_SAMR_PASSWORDS: + if (base_acct_flags & (ACB_WSTRUST|ACB_DOMTRUST|ACB_SVRTRUST)) { + char simple_pass[9]; + char *v = generate_random_str(tctx, 1); + + ZERO_STRUCT(simple_pass); + memset(simple_pass, *v, sizeof(simple_pass) - 1); + + torture_comment(tctx, "Testing machine account password policy rules\n"); + + /* Workstation trust accounts don't seem to need to honour password quality policy */ + if (!test_SetUserPassEx(p, tctx, user_handle, true, &password)) { + ret = false; + } + + if (!test_ChangePasswordUser2(p, tctx, base_acct_name, &password, simple_pass, false)) { + ret = false; + } + + /* reset again, to allow another 'user' password change */ + if (!test_SetUserPassEx(p, tctx, user_handle, true, &password)) { + ret = false; + } + + /* Try a 'short' password */ + if (!test_ChangePasswordUser2(p, tctx, base_acct_name, &password, samr_rand_pass(tctx, 4), false)) { + ret = false; + } + + /* Try a compleatly random password */ + if (!test_ChangePasswordRandomBytes(p, tctx, base_acct_name, user_handle, &password)) { + ret = false; + } + } + + for (i = 0; password_fields[i]; i++) { + if (!test_SetUserPass_23(p, tctx, user_handle, password_fields[i], &password)) { + ret = false; + } + + /* check it was set right */ + if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) { + ret = false; + } + } + + for (i = 0; password_fields[i]; i++) { + if (!test_SetUserPass_25(p, tctx, user_handle, password_fields[i], &password)) { + ret = false; + } + + /* check it was set right */ + if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) { + ret = false; + } + } + + if (!test_SetUserPass_31(p, tctx, user_handle, false, &password)) { + ret = false; + } + + for (i = 0; password_fields[i]; i++) { + if (!test_SetUserPass_32(p, tctx, user_handle, password_fields[i], &password)) { + ret = false; + } + + /* check it was set right */ + if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) { + ret = false; + } + } + + if (!test_ChangePassword(p, tctx, base_acct_name, domain_handle, &password)) { + ret = false; + } + + if (!test_SetUserPassEx(p, tctx, user_handle, false, &password)) { + ret = false; + } + + if (!test_ChangePassword(p, tctx, base_acct_name, domain_handle, &password)) { + ret = false; + } + + if (!test_SetUserPass_18(p, tctx, user_handle, &password)) { + ret = false; + } + + if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) { + ret = false; + } + + for (i = 0; password_fields[i]; i++) { + + if (password_fields[i] == SAMR_FIELD_LM_PASSWORD_PRESENT) { + /* we need to skip as that would break + * the ChangePasswordUser3 verify */ + continue; + } + + if (!test_SetUserPass_21(p, tctx, user_handle, password_fields[i], &password)) { + ret = false; + } + + /* check it was set right */ + if (!test_ChangePasswordUser3(p, tctx, base_acct_name, 0, &password, NULL, 0, false)) { + ret = false; + } + } + + q.in.user_handle = user_handle; + q.in.level = 5; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level %u failed - %s\n", + q.in.level, nt_errstr(q.out.result)); + ret = false; + } else { + uint32_t expected_flags = (base_acct_flags | ACB_PWNOTREQ | ACB_DISABLED); + if ((info->info5.acct_flags) != expected_flags) { + /* FIXME: GD */ + if (!torture_setting_bool(tctx, "samba3", false)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5 failed, it returned 0x%08x when we expected flags of 0x%08x\n", + info->info5.acct_flags, + expected_flags); + ret = false; + } + } + if (info->info5.rid != rid) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5 failed, it returned %u when we expected rid of %u\n", + info->info5.rid, rid); + + } + } + + break; + + case TORTURE_SAMR_PASSWORDS_PWDLASTSET: + + /* test last password change timestamp behaviour */ + torture_assert(tctx, test_SetPassword_pwdlastset(p, tctx, base_acct_flags, + base_acct_name, + user_handle, &password, + machine_credentials), + "pwdLastSet test failed\n"); + break; + + case TORTURE_SAMR_PASSWORDS_BADPWDCOUNT: + + /* test bad pwd count change behaviour */ + torture_assert(tctx, test_Password_badpwdcount_wrap(p, tctx, base_acct_flags, + base_acct_name, + domain_handle, + user_handle, &password, + machine_credentials), + "badPwdCount test failed\n"); + break; + + case TORTURE_SAMR_PASSWORDS_LOCKOUT: + + torture_assert(tctx, test_Password_lockout_wrap(p, tctx, base_acct_flags, + base_acct_name, + domain_handle, + user_handle, &password, + machine_credentials), + "Lockout test failed"); + break; + + + case TORTURE_SAMR_USER_PRIVILEGES: { + + struct dcerpc_pipe *lp; + struct policy_handle *lsa_handle; + struct dcerpc_binding_handle *lb; + + status = torture_rpc_connection(tctx, &lp, &ndr_table_lsarpc); + torture_assert_ntstatus_ok(tctx, status, "Failed to open LSA pipe"); + lb = lp->binding_handle; + + if (!test_lsa_OpenPolicy2(lb, tctx, &lsa_handle)) { + ret = false; + } + + if (!test_DeleteUser_with_privs(p, lp, tctx, + domain_handle, lsa_handle, user_handle, + domain_sid, rid, + machine_credentials)) { + ret = false; + } + + if (!test_lsa_Close(lb, tctx, lsa_handle)) { + ret = false; + } + + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "privileged user delete test failed\n"); + } + + break; + } + case TORTURE_SAMR_OTHER: + case TORTURE_SAMR_MANY_ACCOUNTS: + case TORTURE_SAMR_MANY_GROUPS: + case TORTURE_SAMR_MANY_ALIASES: + /* We just need the account to exist */ + break; + } + return ret; +} + +static bool test_alias_ops(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *alias_handle, + const struct dom_sid *domain_sid) +{ + bool ret = true; + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (!test_QuerySecurity(b, tctx, alias_handle)) { + ret = false; + } + } + + if (!test_QueryAliasInfo(b, tctx, alias_handle)) { + ret = false; + } + + if (!test_SetAliasInfo(b, tctx, alias_handle)) { + ret = false; + } + + if (!test_AddMemberToAlias(b, tctx, alias_handle, domain_sid)) { + ret = false; + } + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping MultipleMembers Alias tests against Samba\n"); + return ret; + } + + if (!test_AddMultipleMembersToAlias(b, tctx, alias_handle)) { + ret = false; + } + + return ret; +} + + +static bool test_DeleteUser(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *user_handle) +{ + struct samr_DeleteUser d; + torture_comment(tctx, "Testing DeleteUser\n"); + + d.in.user_handle = user_handle; + d.out.user_handle = user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteUser_r(b, tctx, &d), + "DeleteUser failed"); + torture_assert_ntstatus_ok(tctx, d.out.result, "DeleteUser"); + + return true; +} + +bool test_DeleteUser_byname(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *name) +{ + NTSTATUS status; + struct samr_DeleteUser d; + struct policy_handle user_handle; + uint32_t rid; + + status = test_LookupName(b, tctx, handle, name, &rid); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + status = test_OpenUser_byname(b, tctx, handle, name, &user_handle); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + d.in.user_handle = &user_handle; + d.out.user_handle = &user_handle; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteUser_r(b, tctx, &d), + "DeleteUser failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + status = d.out.result; + goto failed; + } + + return true; + +failed: + torture_result(tctx, TORTURE_FAIL, "DeleteUser_byname(%s) failed - %s\n", name, nt_errstr(status)); + return false; +} + + +static bool test_DeleteGroup_byname(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *name) +{ + NTSTATUS status; + struct samr_OpenGroup r; + struct samr_DeleteDomainGroup d; + struct policy_handle group_handle; + uint32_t rid; + + status = test_LookupName(b, tctx, handle, name, &rid); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.group_handle = &group_handle; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenGroup_r(b, tctx, &r), + "OpenGroup failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + status = r.out.result; + goto failed; + } + + d.in.group_handle = &group_handle; + d.out.group_handle = &group_handle; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteDomainGroup_r(b, tctx, &d), + "DeleteDomainGroup failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + status = d.out.result; + goto failed; + } + + return true; + +failed: + torture_result(tctx, TORTURE_FAIL, "DeleteGroup_byname(%s) failed - %s\n", name, nt_errstr(status)); + return false; +} + + +static bool test_DeleteAlias_byname(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *name) +{ + NTSTATUS status; + struct samr_OpenAlias r; + struct samr_DeleteDomAlias d; + struct policy_handle alias_handle; + uint32_t rid; + + torture_comment(tctx, "Testing DeleteAlias_byname\n"); + + status = test_LookupName(b, tctx, domain_handle, name, &rid); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + r.in.domain_handle = domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.alias_handle = &alias_handle; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenAlias_r(b, tctx, &r), + "OpenAlias failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + status = r.out.result; + goto failed; + } + + d.in.alias_handle = &alias_handle; + d.out.alias_handle = &alias_handle; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteDomAlias_r(b, tctx, &d), + "DeleteDomAlias failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + status = d.out.result; + goto failed; + } + + return true; + +failed: + torture_result(tctx, TORTURE_FAIL, "DeleteAlias_byname(%s) failed - %s\n", name, nt_errstr(status)); + return false; +} + +static bool test_DeleteAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *alias_handle) +{ + struct samr_DeleteDomAlias d; + bool ret = true; + + torture_comment(tctx, "Testing DeleteAlias\n"); + + d.in.alias_handle = alias_handle; + d.out.alias_handle = alias_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteDomAlias_r(b, tctx, &d), + "DeleteDomAlias failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + torture_result(tctx, TORTURE_FAIL, "DeleteAlias failed - %s\n", nt_errstr(d.out.result)); + ret = false; + } + + return ret; +} + +static bool test_CreateAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *alias_name, + struct policy_handle *alias_handle, + const struct dom_sid *domain_sid, + bool test_alias) +{ + struct samr_CreateDomAlias r; + struct lsa_String name; + uint32_t rid; + bool ret = true; + + init_lsa_String(&name, alias_name); + r.in.domain_handle = domain_handle; + r.in.alias_name = &name; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.alias_handle = alias_handle; + r.out.rid = &rid; + + torture_comment(tctx, "Testing CreateAlias (%s)\n", r.in.alias_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateDomAlias_r(b, tctx, &r), + "CreateDomAlias failed"); + + if (dom_sid_equal(domain_sid, dom_sid_parse_talloc(tctx, SID_BUILTIN))) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, "Server correctly refused create of '%s'\n", r.in.alias_name->string); + return true; + } else { + torture_result(tctx, TORTURE_FAIL, "Server should have refused create of '%s', got %s instead\n", r.in.alias_name->string, + nt_errstr(r.out.result)); + return false; + } + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ALIAS_EXISTS)) { + if (!test_DeleteAlias_byname(b, tctx, domain_handle, r.in.alias_name->string)) { + return false; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateDomAlias_r(b, tctx, &r), + "CreateDomAlias failed"); + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "CreateAlias failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + if (!test_alias) { + return ret; + } + + if (!test_alias_ops(b, tctx, alias_handle, domain_sid)) { + ret = false; + } + + return ret; +} + +static bool test_ChangePassword(struct dcerpc_pipe *p, + struct torture_context *tctx, + const char *acct_name, + struct policy_handle *domain_handle, char **password) +{ + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!*password) { + return false; + } + + if (!test_ChangePasswordUser(b, tctx, acct_name, domain_handle, password)) { + ret = false; + } + + if (!test_ChangePasswordUser2(p, tctx, acct_name, password, 0, true)) { + ret = false; + } + + if (!test_OemChangePasswordUser2(p, tctx, acct_name, domain_handle, password)) { + ret = false; + } + + /* test what happens when setting the old password again */ + if (!test_ChangePasswordUser3(p, tctx, acct_name, 0, password, *password, 0, true)) { + ret = false; + } + + { + char simple_pass[9]; + char *v = generate_random_str(tctx, 1); + + ZERO_STRUCT(simple_pass); + memset(simple_pass, *v, sizeof(simple_pass) - 1); + + /* test what happens when picking a simple password */ + if (!test_ChangePasswordUser3(p, tctx, acct_name, 0, password, simple_pass, 0, true)) { + ret = false; + } + } + + /* set samr_SetDomainInfo level 1 with min_length 5 */ + { + struct samr_QueryDomainInfo r; + union samr_DomainInfo *info = NULL; + struct samr_SetDomainInfo s; + uint16_t len_old, len; + uint32_t pwd_prop_old; + int64_t min_pwd_age_old; + + len = 5; + + r.in.domain_handle = domain_handle; + r.in.level = 1; + r.out.info = &info; + + torture_comment(tctx, "Testing samr_QueryDomainInfo level 1\n"); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo_r(b, tctx, &r), + "QueryDomainInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + return false; + } + + s.in.domain_handle = domain_handle; + s.in.level = 1; + s.in.info = info; + + /* remember the old min length, so we can reset it */ + len_old = s.in.info->info1.min_password_length; + s.in.info->info1.min_password_length = len; + pwd_prop_old = s.in.info->info1.password_properties; + /* turn off password complexity checks for this test */ + s.in.info->info1.password_properties &= ~DOMAIN_PASSWORD_COMPLEX; + + min_pwd_age_old = s.in.info->info1.min_password_age; + s.in.info->info1.min_password_age = 0; + + torture_comment(tctx, "Testing samr_SetDomainInfo level 1\n"); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDomainInfo_r(b, tctx, &s), + "SetDomainInfo failed"); + if (!NT_STATUS_IS_OK(s.out.result)) { + return false; + } + + torture_comment(tctx, "calling test_ChangePasswordUser3 with too short password\n"); + + if (!test_ChangePasswordUser3(p, tctx, acct_name, len - 1, password, NULL, 0, true)) { + ret = false; + } + + s.in.info->info1.min_password_length = len_old; + s.in.info->info1.password_properties = pwd_prop_old; + s.in.info->info1.min_password_age = min_pwd_age_old; + + torture_comment(tctx, "Testing samr_SetDomainInfo level 1\n"); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDomainInfo_r(b, tctx, &s), + "SetDomainInfo failed"); + if (!NT_STATUS_IS_OK(s.out.result)) { + return false; + } + + } + + { + struct samr_OpenUser r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct samr_LookupNames n; + struct policy_handle user_handle; + struct samr_Ids rids, types; + + n.in.domain_handle = domain_handle; + n.in.num_names = 1; + n.in.names = talloc_array(tctx, struct lsa_String, 1); + n.in.names[0].string = acct_name; + n.out.rids = &rids; + n.out.types = &types; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupNames_r(b, tctx, &n), + "LookupNames failed"); + if (!NT_STATUS_IS_OK(n.out.result)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames failed - %s\n", nt_errstr(n.out.result)); + return false; + } + + r.in.domain_handle = domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = n.out.rids->ids[0]; + r.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenUser_r(b, tctx, &r), + "OpenUser failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenUser(%u) failed - %s\n", n.out.rids->ids[0], nt_errstr(r.out.result)); + return false; + } + + q.in.user_handle = &user_handle; + q.in.level = 5; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo failed - %s\n", nt_errstr(q.out.result)); + return false; + } + + torture_comment(tctx, "calling test_ChangePasswordUser3 with too early password change\n"); + + if (!test_ChangePasswordUser3(p, tctx, acct_name, 0, password, NULL, + info->info5.last_password_change, true)) { + ret = false; + } + } + + /* we change passwords twice - this has the effect of verifying + they were changed correctly for the final call */ + if (!test_ChangePasswordUser3(p, tctx, acct_name, 0, password, NULL, 0, true)) { + ret = false; + } + + if (!test_ChangePasswordUser3(p, tctx, acct_name, 0, password, NULL, 0, true)) { + ret = false; + } + + if (!test_ChangePasswordUser4(p, tctx, acct_name, 0, password, NULL)) { + ret = false; + } + + return ret; +} + +static bool test_CreateUser(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *user_name, + struct policy_handle *user_handle_out, + struct dom_sid *domain_sid, + enum torture_samr_choice which_ops, + struct cli_credentials *machine_credentials, + bool test_user) +{ + + TALLOC_CTX *user_ctx; + + struct samr_CreateUser r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct samr_DeleteUser d; + uint32_t rid; + + /* This call creates a 'normal' account - check that it really does */ + const uint32_t acct_flags = ACB_NORMAL; + struct lsa_String name; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct policy_handle user_handle; + user_ctx = talloc_named(tctx, 0, "test_CreateUser2 per-user context"); + init_lsa_String(&name, user_name); + + r.in.domain_handle = domain_handle; + r.in.account_name = &name; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.user_handle = &user_handle; + r.out.rid = &rid; + + torture_comment(tctx, "Testing CreateUser(%s)\n", r.in.account_name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateUser_r(b, user_ctx, &r), + "CreateUser failed"); + + if (dom_sid_equal(domain_sid, dom_sid_parse_talloc(tctx, SID_BUILTIN))) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_comment(tctx, "Server correctly refused create of '%s'\n", r.in.account_name->string); + return true; + } else { + torture_result(tctx, TORTURE_FAIL, "Server should have refused create of '%s', got %s instead\n", r.in.account_name->string, + nt_errstr(r.out.result)); + return false; + } + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_USER_EXISTS)) { + if (!test_DeleteUser_byname(b, tctx, domain_handle, r.in.account_name->string)) { + talloc_free(user_ctx); + return false; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateUser_r(b, user_ctx, &r), + "CreateUser failed"); + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + talloc_free(user_ctx); + torture_result(tctx, TORTURE_FAIL, "CreateUser failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + if (!test_user) { + if (user_handle_out) { + *user_handle_out = user_handle; + } + return ret; + } + + { + q.in.user_handle = &user_handle; + q.in.level = 16; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, user_ctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level %u failed - %s\n", + q.in.level, nt_errstr(q.out.result)); + ret = false; + } else { + if ((info->info16.acct_flags & acct_flags) != acct_flags) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 16 failed, it returned 0x%08x when we expected flags of 0x%08x\n", + info->info16.acct_flags, + acct_flags); + ret = false; + } + } + + if (!test_user_ops(p, tctx, &user_handle, domain_handle, + domain_sid, acct_flags, name.string, which_ops, + machine_credentials)) { + ret = false; + } + + if (user_handle_out) { + *user_handle_out = user_handle; + } else { + torture_comment(tctx, "Testing DeleteUser (createuser test)\n"); + + d.in.user_handle = &user_handle; + d.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteUser_r(b, user_ctx, &d), + "DeleteUser failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + torture_result(tctx, TORTURE_FAIL, "DeleteUser failed - %s\n", nt_errstr(d.out.result)); + ret = false; + } + } + + } + + talloc_free(user_ctx); + + return ret; +} + + +static bool test_CreateUser2(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *domain_handle, + struct dom_sid *domain_sid, + enum torture_samr_choice which_ops, + struct cli_credentials *machine_credentials) +{ + struct samr_CreateUser2 r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct samr_DeleteUser d; + struct policy_handle user_handle; + uint32_t rid; + struct lsa_String name; + bool ret = true; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct { + uint32_t acct_flags; + const char *account_name; + NTSTATUS nt_status; + } account_types[] = { + { ACB_NORMAL, TEST_ACCOUNT_NAME, NT_STATUS_OK }, + { ACB_NORMAL | ACB_DISABLED, TEST_ACCOUNT_NAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_NORMAL | ACB_PWNOEXP, TEST_ACCOUNT_NAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_WSTRUST, TEST_MACHINENAME, NT_STATUS_OK }, + { ACB_WSTRUST | ACB_DISABLED, TEST_MACHINENAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_WSTRUST | ACB_PWNOEXP, TEST_MACHINENAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_SVRTRUST, TEST_MACHINENAME, NT_STATUS_OK }, + { ACB_SVRTRUST | ACB_DISABLED, TEST_MACHINENAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_SVRTRUST | ACB_PWNOEXP, TEST_MACHINENAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_DOMTRUST, TEST_DOMAINNAME, NT_STATUS_ACCESS_DENIED }, + { ACB_DOMTRUST | ACB_DISABLED, TEST_DOMAINNAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_DOMTRUST | ACB_PWNOEXP, TEST_DOMAINNAME, NT_STATUS_INVALID_PARAMETER }, + { 0, TEST_ACCOUNT_NAME, NT_STATUS_INVALID_PARAMETER }, + { ACB_DISABLED, TEST_ACCOUNT_NAME, NT_STATUS_INVALID_PARAMETER }, + { 0, NULL, NT_STATUS_INVALID_PARAMETER } + }; + + for (i = 0; account_types[i].account_name; i++) { + TALLOC_CTX *user_ctx; + uint32_t acct_flags = account_types[i].acct_flags; + uint32_t access_granted; + user_ctx = talloc_named(tctx, 0, "test_CreateUser2 per-user context"); + init_lsa_String(&name, account_types[i].account_name); + + r.in.domain_handle = domain_handle; + r.in.account_name = &name; + r.in.acct_flags = acct_flags; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.user_handle = &user_handle; + r.out.access_granted = &access_granted; + r.out.rid = &rid; + + torture_comment(tctx, "Testing CreateUser2(%s, 0x%x)\n", r.in.account_name->string, acct_flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateUser2_r(b, user_ctx, &r), + "CreateUser2 failed"); + + if (dom_sid_equal(domain_sid, dom_sid_parse_talloc(tctx, SID_BUILTIN))) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED) || NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_PARAMETER)) { + torture_comment(tctx, "Server correctly refused create of '%s'\n", r.in.account_name->string); + continue; + } else { + torture_result(tctx, TORTURE_FAIL, "Server should have refused create of '%s', got %s instead\n", r.in.account_name->string, + nt_errstr(r.out.result)); + ret = false; + continue; + } + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_USER_EXISTS)) { + if (!test_DeleteUser_byname(b, tctx, domain_handle, r.in.account_name->string)) { + talloc_free(user_ctx); + ret = false; + continue; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateUser2_r(b, user_ctx, &r), + "CreateUser2 failed"); + + } + if (!NT_STATUS_EQUAL(r.out.result, account_types[i].nt_status)) { + torture_result(tctx, TORTURE_FAIL, "CreateUser2 failed gave incorrect error return - %s (should be %s)\n", + nt_errstr(r.out.result), nt_errstr(account_types[i].nt_status)); + ret = false; + } + + if (NT_STATUS_IS_OK(r.out.result)) { + q.in.user_handle = &user_handle; + q.in.level = 5; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, user_ctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level %u failed - %s\n", + q.in.level, nt_errstr(q.out.result)); + ret = false; + } else { + uint32_t expected_flags = (acct_flags | ACB_PWNOTREQ | ACB_DISABLED); + if (acct_flags == ACB_NORMAL) { + expected_flags |= ACB_PW_EXPIRED; + } + if ((info->info5.acct_flags) != expected_flags) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5 failed, it returned 0x%08x when we expected flags of 0x%08x\n", + info->info5.acct_flags, + expected_flags); + ret = false; + } + switch (acct_flags) { + case ACB_SVRTRUST: + if (info->info5.primary_gid != DOMAIN_RID_DCS) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5: DC should have had Primary Group %d, got %d\n", + DOMAIN_RID_DCS, info->info5.primary_gid); + ret = false; + } + break; + case ACB_WSTRUST: + if (info->info5.primary_gid != DOMAIN_RID_DOMAIN_MEMBERS) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5: Domain Member should have had Primary Group %d, got %d\n", + DOMAIN_RID_DOMAIN_MEMBERS, info->info5.primary_gid); + ret = false; + } + break; + case ACB_NORMAL: + if (info->info5.primary_gid != DOMAIN_RID_USERS) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 5: Users should have had Primary Group %d, got %d\n", + DOMAIN_RID_USERS, info->info5.primary_gid); + ret = false; + } + break; + } + } + + if (!test_user_ops(p, tctx, &user_handle, domain_handle, + domain_sid, acct_flags, name.string, which_ops, + machine_credentials)) { + ret = false; + } + + if (!ndr_policy_handle_empty(&user_handle)) { + torture_comment(tctx, "Testing DeleteUser (createuser2 test)\n"); + + d.in.user_handle = &user_handle; + d.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteUser_r(b, user_ctx, &d), + "DeleteUser failed"); + if (!NT_STATUS_IS_OK(d.out.result)) { + torture_result(tctx, TORTURE_FAIL, "DeleteUser failed - %s\n", nt_errstr(d.out.result)); + ret = false; + } + } + } + talloc_free(user_ctx); + } + + return ret; +} + +static bool test_QueryAliasInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryAliasInfo r; + union samr_AliasInfo *info; + uint16_t levels[] = {1, 2, 3}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryAliasInfo level %u\n", levels[i]); + + r.in.alias_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryAliasInfo_r(b, tctx, &r), + "QueryAliasInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryAliasInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_QueryGroupInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryGroupInfo r; + union samr_GroupInfo *info; + uint16_t levels[] = {1, 2, 3, 4, 5}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryGroupInfo level %u\n", levels[i]); + + r.in.group_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupInfo_r(b, tctx, &r), + "QueryGroupInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryGroupInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_QueryGroupMember(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryGroupMember r; + struct samr_RidAttrArray *rids = NULL; + bool ret = true; + + torture_comment(tctx, "Testing QueryGroupMember\n"); + + r.in.group_handle = handle; + r.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupMember_r(b, tctx, &r), + "QueryGroupMember failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryGroupMember failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + return ret; +} + + +static bool test_SetGroupInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryGroupInfo r; + union samr_GroupInfo *info; + struct samr_SetGroupInfo s; + uint16_t levels[] = {1, 2, 3, 4}; + uint16_t set_ok[] = {0, 1, 1, 1}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryGroupInfo level %u\n", levels[i]); + + r.in.group_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupInfo_r(b, tctx, &r), + "QueryGroupInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryGroupInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + + torture_comment(tctx, "Testing SetGroupInfo level %u\n", levels[i]); + + s.in.group_handle = handle; + s.in.level = levels[i]; + s.in.info = *r.out.info; + +#if 0 + /* disabled this, as it changes the name only from the point of view of samr, + but leaves the name from the point of view of w2k3 internals (and ldap). This means + the name is still reserved, so creating the old name fails, but deleting by the old name + also fails */ + if (s.in.level == 2) { + init_lsa_String(&s.in.info->string, "NewName"); + } +#endif + + if (s.in.level == 4) { + init_lsa_String(&s.in.info->description, "test description"); + } + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetGroupInfo_r(b, tctx, &s), + "SetGroupInfo failed"); + if (set_ok[i]) { + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetGroupInfo level %u failed - %s\n", + r.in.level, nt_errstr(s.out.result)); + ret = false; + continue; + } + } else { + if (!NT_STATUS_EQUAL(NT_STATUS_INVALID_INFO_CLASS, s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetGroupInfo level %u gave %s - should have been NT_STATUS_INVALID_INFO_CLASS\n", + r.in.level, nt_errstr(s.out.result)); + ret = false; + continue; + } + } + } + + return ret; +} + +static bool test_QueryUserInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryUserInfo r; + union samr_UserInfo *info; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 16, 17, 20, 21}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryUserInfo level %u\n", levels[i]); + + r.in.user_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_QueryUserInfo2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryUserInfo2 r; + union samr_UserInfo *info; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 16, 17, 20, 21}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryUserInfo2 level %u\n", levels[i]); + + r.in.user_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo2_r(b, tctx, &r), + "QueryUserInfo2 failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo2 level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_OpenUser(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, uint32_t rid) +{ + struct samr_OpenUser r; + struct policy_handle user_handle; + bool ret = true; + + torture_comment(tctx, "Testing OpenUser(%u)\n", rid); + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenUser_r(b, tctx, &r), + "OpenUser failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenUser(%u) failed - %s\n", rid, nt_errstr(r.out.result)); + return false; + } + + if (!test_QuerySecurity(b, tctx, &user_handle)) { + ret = false; + } + + if (!test_QueryUserInfo(b, tctx, &user_handle)) { + ret = false; + } + + if (!test_QueryUserInfo2(b, tctx, &user_handle)) { + ret = false; + } + + if (!test_GetUserPwInfo(b, tctx, &user_handle)) { + ret = false; + } + + if (!test_GetGroupsForUser(b, tctx, &user_handle)) { + ret = false; + } + + if (!test_samr_handle_Close(b, tctx, &user_handle)) { + ret = false; + } + + return ret; +} + +static bool test_OpenGroup(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, uint32_t rid) +{ + struct samr_OpenGroup r; + struct policy_handle group_handle; + bool ret = true; + + torture_comment(tctx, "Testing OpenGroup(%u)\n", rid); + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.group_handle = &group_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenGroup_r(b, tctx, &r), + "OpenGroup failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenGroup(%u) failed - %s\n", rid, nt_errstr(r.out.result)); + return false; + } + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (!test_QuerySecurity(b, tctx, &group_handle)) { + ret = false; + } + } + + if (!test_QueryGroupInfo(b, tctx, &group_handle)) { + ret = false; + } + + if (!test_QueryGroupMember(b, tctx, &group_handle)) { + ret = false; + } + + if (!test_samr_handle_Close(b, tctx, &group_handle)) { + ret = false; + } + + return ret; +} + +static bool test_OpenAlias(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, uint32_t rid) +{ + struct samr_OpenAlias r; + struct policy_handle alias_handle; + bool ret = true; + + torture_comment(tctx, "Testing OpenAlias(%u)\n", rid); + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.alias_handle = &alias_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenAlias_r(b, tctx, &r), + "OpenAlias failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenAlias(%u) failed - %s\n", rid, nt_errstr(r.out.result)); + return false; + } + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (!test_QuerySecurity(b, tctx, &alias_handle)) { + ret = false; + } + } + + if (!test_QueryAliasInfo(b, tctx, &alias_handle)) { + ret = false; + } + + if (!test_GetMembersInAlias(b, tctx, &alias_handle)) { + ret = false; + } + + if (!test_samr_handle_Close(b, tctx, &alias_handle)) { + ret = false; + } + + return ret; +} + +static bool check_mask(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, uint32_t rid, + uint32_t acct_flag_mask) +{ + struct samr_OpenUser r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct policy_handle user_handle; + bool ret = true; + + torture_comment(tctx, "Testing OpenUser(%u)\n", rid); + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenUser_r(b, tctx, &r), + "OpenUser failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenUser(%u) failed - %s\n", rid, nt_errstr(r.out.result)); + return false; + } + + q.in.user_handle = &user_handle; + q.in.level = 16; + q.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo level 16 failed - %s\n", + nt_errstr(q.out.result)); + ret = false; + } else { + if ((acct_flag_mask & info->info16.acct_flags) == 0) { + torture_result(tctx, TORTURE_FAIL, "Server failed to filter for 0x%x, allowed 0x%x (%d) on EnumDomainUsers\n", + acct_flag_mask, info->info16.acct_flags, rid); + ret = false; + } + } + + if (!test_samr_handle_Close(b, tctx, &user_handle)) { + ret = false; + } + + return ret; +} + +static bool test_EnumDomainUsers_all(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_EnumDomainUsers r; + uint32_t mask, resume_handle=0; + int i, mask_idx; + bool ret = true; + struct samr_LookupNames n; + struct samr_LookupRids lr ; + struct lsa_Strings names; + struct samr_Ids rids, types; + struct samr_SamArray *sam = NULL; + uint32_t num_entries = 0; + + uint32_t masks[] = {ACB_NORMAL, ACB_DOMTRUST, ACB_WSTRUST, + ACB_DISABLED, ACB_NORMAL | ACB_DISABLED, + ACB_SVRTRUST | ACB_DOMTRUST | ACB_WSTRUST, + ACB_PWNOEXP, 0}; + + torture_comment(tctx, "Testing EnumDomainUsers\n"); + + for (mask_idx=0;mask_idx<ARRAY_SIZE(masks);mask_idx++) { + r.in.domain_handle = handle; + r.in.resume_handle = &resume_handle; + r.in.acct_flags = mask = masks[mask_idx]; + r.in.max_size = (uint32_t)-1; + r.out.resume_handle = &resume_handle; + r.out.num_entries = &num_entries; + r.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainUsers_r(b, tctx, &r), + "EnumDomainUsers failed"); + if (!NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) && + !NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "EnumDomainUsers failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + torture_assert(tctx, sam, "EnumDomainUsers failed: r.out.sam unexpectedly NULL"); + + if (sam->count == 0) { + continue; + } + + for (i=0;i<sam->count;i++) { + if (mask) { + if (!check_mask(b, tctx, handle, sam->entries[i].idx, mask)) { + ret = false; + } + } else if (!test_OpenUser(b, tctx, handle, sam->entries[i].idx)) { + ret = false; + } + } + } + + torture_comment(tctx, "Testing LookupNames\n"); + n.in.domain_handle = handle; + n.in.num_names = sam->count; + n.in.names = talloc_array(tctx, struct lsa_String, sam->count); + n.out.rids = &rids; + n.out.types = &types; + for (i=0;i<sam->count;i++) { + n.in.names[i].string = sam->entries[i].name.string; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupNames_r(b, tctx, &n), + "LookupNames failed"); + if (!NT_STATUS_IS_OK(n.out.result)) { + torture_result(tctx, TORTURE_FAIL, "LookupNames failed - %s\n", nt_errstr(n.out.result)); + ret = false; + } + + + torture_comment(tctx, "Testing LookupRids\n"); + lr.in.domain_handle = handle; + lr.in.num_rids = sam->count; + lr.in.rids = talloc_array(tctx, uint32_t, sam->count); + lr.out.names = &names; + lr.out.types = &types; + for (i=0;i<sam->count;i++) { + lr.in.rids[i] = sam->entries[i].idx; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupRids_r(b, tctx, &lr), + "LookupRids failed"); + torture_assert_ntstatus_ok(tctx, lr.out.result, "LookupRids"); + + return ret; +} + +/* + try blasting the server with a bunch of sync requests +*/ +static bool test_EnumDomainUsers_async(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_EnumDomainUsers r; + uint32_t resume_handle=0; + int i; +#define ASYNC_COUNT 100 + struct tevent_req *req[ASYNC_COUNT]; + + if (!torture_setting_bool(tctx, "dangerous", false)) { + torture_skip(tctx, "samr async test disabled - enable dangerous tests to use\n"); + } + + torture_comment(tctx, "Testing EnumDomainUsers_async\n"); + + r.in.domain_handle = handle; + r.in.resume_handle = &resume_handle; + r.in.acct_flags = 0; + r.in.max_size = (uint32_t)-1; + r.out.resume_handle = &resume_handle; + + for (i=0;i<ASYNC_COUNT;i++) { + req[i] = dcerpc_samr_EnumDomainUsers_r_send(tctx, tctx->ev, p->binding_handle, &r); + } + + for (i=0;i<ASYNC_COUNT;i++) { + tevent_req_poll(req[i], tctx->ev); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainUsers_r_recv(req[i], tctx), + talloc_asprintf(tctx, "EnumDomainUsers[%d] failed - %s\n", + i, nt_errstr(r.out.result))); + } + + torture_comment(tctx, "%d async requests OK\n", i); + + return true; +} + +static bool test_EnumDomainGroups_all(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_EnumDomainGroups r; + uint32_t resume_handle=0; + struct samr_SamArray *sam = NULL; + uint32_t num_entries = 0; + int i; + bool ret = true; + bool universal_group_found = false; + + torture_comment(tctx, "Testing EnumDomainGroups\n"); + + r.in.domain_handle = handle; + r.in.resume_handle = &resume_handle; + r.in.max_size = (uint32_t)-1; + r.out.resume_handle = &resume_handle; + r.out.num_entries = &num_entries; + r.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainGroups_r(b, tctx, &r), + "EnumDomainGroups failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "EnumDomainGroups failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + if (!sam) { + return false; + } + + for (i=0;i<sam->count;i++) { + if (!test_OpenGroup(b, tctx, handle, sam->entries[i].idx)) { + ret = false; + } + if ((ret == true) && (strcasecmp(sam->entries[i].name.string, + "Enterprise Admins") == 0)) { + universal_group_found = true; + } + } + + /* when we are running this on s4 we should get back at least the + * "Enterprise Admins" universal group. If we don't get a group entry + * at all we probably are performing the test on the builtin domain. + * So ignore this case. */ + if (torture_setting_bool(tctx, "samba4", false)) { + if ((sam->count > 0) && (!universal_group_found)) { + ret = false; + } + } + + return ret; +} + +static bool test_EnumDomainAliases_all(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_EnumDomainAliases r; + uint32_t resume_handle=0; + struct samr_SamArray *sam = NULL; + uint32_t num_entries = 0; + int i; + bool ret = true; + + torture_comment(tctx, "Testing EnumDomainAliases\n"); + + r.in.domain_handle = handle; + r.in.resume_handle = &resume_handle; + r.in.max_size = (uint32_t)-1; + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainAliases_r(b, tctx, &r), + "EnumDomainAliases failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "EnumDomainAliases failed - %s\n", nt_errstr(r.out.result)); + return false; + } + + if (!sam) { + return false; + } + + for (i=0;i<sam->count;i++) { + if (!test_OpenAlias(b, tctx, handle, sam->entries[i].idx)) { + ret = false; + } + } + + return ret; +} + +static bool test_GetDisplayEnumerationIndex(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_GetDisplayEnumerationIndex r; + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5}; + uint16_t ok_lvl[] = {1, 1, 1, 0, 0}; + struct lsa_String name; + uint32_t idx = 0; + int i; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing GetDisplayEnumerationIndex level %u\n", levels[i]); + + init_lsa_String(&name, TEST_ACCOUNT_NAME); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.in.name = &name; + r.out.idx = &idx; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDisplayEnumerationIndex_r(b, tctx, &r), + "GetDisplayEnumerationIndex failed"); + + if (ok_lvl[i] && + !NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(NT_STATUS_NO_MORE_ENTRIES, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "GetDisplayEnumerationIndex level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + + init_lsa_String(&name, "zzzzzzzz"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDisplayEnumerationIndex_r(b, tctx, &r), + "GetDisplayEnumerationIndex failed"); + + if (ok_lvl[i] && !NT_STATUS_EQUAL(NT_STATUS_NO_MORE_ENTRIES, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "GetDisplayEnumerationIndex level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_GetDisplayEnumerationIndex2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_GetDisplayEnumerationIndex2 r; + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5}; + uint16_t ok_lvl[] = {1, 1, 1, 0, 0}; + struct lsa_String name; + uint32_t idx = 0; + int i; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing GetDisplayEnumerationIndex2 level %u\n", levels[i]); + + init_lsa_String(&name, TEST_ACCOUNT_NAME); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.in.name = &name; + r.out.idx = &idx; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDisplayEnumerationIndex2_r(b, tctx, &r), + "GetDisplayEnumerationIndex2 failed"); + if (ok_lvl[i] && + !NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(NT_STATUS_NO_MORE_ENTRIES, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "GetDisplayEnumerationIndex2 level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + + init_lsa_String(&name, "zzzzzzzz"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDisplayEnumerationIndex2_r(b, tctx, &r), + "GetDisplayEnumerationIndex2 failed"); + if (ok_lvl[i] && !NT_STATUS_EQUAL(NT_STATUS_NO_MORE_ENTRIES, r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "GetDisplayEnumerationIndex2 level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +#define STRING_EQUAL_QUERY(s1, s2, user) \ + if (s1.string == NULL && s2.string != NULL && s2.string[0] == '\0') { \ + /* odd, but valid */ \ + } else if ((s1.string && !s2.string) || (s2.string && !s1.string) || strcmp(s1.string, s2.string)) { \ + torture_result(tctx, TORTURE_FAIL, "%s mismatch for %s: %s != %s (%s)\n", \ + #s1, user.string, s1.string, s2.string, __location__); \ + ret = false; \ + } +#define INT_EQUAL_QUERY(s1, s2, user) \ + if (s1 != s2) { \ + torture_result(tctx, TORTURE_FAIL, "%s mismatch for %s: 0x%llx != 0x%llx (%s)\n", \ + #s1, user.string, (unsigned long long)s1, (unsigned long long)s2, __location__); \ + ret = false; \ + } + +static bool test_each_DisplayInfo_user(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct samr_QueryDisplayInfo *querydisplayinfo, + bool *seen_testuser) +{ + struct samr_OpenUser r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct policy_handle user_handle; + int i, ret = true; + r.in.domain_handle = querydisplayinfo->in.domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + for (i = 0; ; i++) { + switch (querydisplayinfo->in.level) { + case 1: + if (i >= querydisplayinfo->out.info->info1.count) { + return ret; + } + r.in.rid = querydisplayinfo->out.info->info1.entries[i].rid; + break; + case 2: + if (i >= querydisplayinfo->out.info->info2.count) { + return ret; + } + r.in.rid = querydisplayinfo->out.info->info2.entries[i].rid; + break; + case 3: + /* Groups */ + case 4: + case 5: + /* Not interested in validating just the account name */ + return true; + } + + r.out.user_handle = &user_handle; + + switch (querydisplayinfo->in.level) { + case 1: + case 2: + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenUser_r(b, tctx, &r), + "OpenUser failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "OpenUser(%u) failed - %s\n", r.in.rid, nt_errstr(r.out.result)); + return false; + } + } + + q.in.user_handle = &user_handle; + q.in.level = 21; + q.out.info = &info; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &q), + "QueryUserInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryUserInfo(%u) failed - %s\n", r.in.rid, nt_errstr(r.out.result)); + return false; + } + + switch (querydisplayinfo->in.level) { + case 1: + if (seen_testuser && strcmp(info->info21.account_name.string, TEST_ACCOUNT_NAME) == 0) { + *seen_testuser = true; + } + STRING_EQUAL_QUERY(querydisplayinfo->out.info->info1.entries[i].full_name, + info->info21.full_name, info->info21.account_name); + STRING_EQUAL_QUERY(querydisplayinfo->out.info->info1.entries[i].account_name, + info->info21.account_name, info->info21.account_name); + STRING_EQUAL_QUERY(querydisplayinfo->out.info->info1.entries[i].description, + info->info21.description, info->info21.account_name); + INT_EQUAL_QUERY(querydisplayinfo->out.info->info1.entries[i].rid, + info->info21.rid, info->info21.account_name); + INT_EQUAL_QUERY(querydisplayinfo->out.info->info1.entries[i].acct_flags, + info->info21.acct_flags, info->info21.account_name); + + break; + case 2: + STRING_EQUAL_QUERY(querydisplayinfo->out.info->info2.entries[i].account_name, + info->info21.account_name, info->info21.account_name); + STRING_EQUAL_QUERY(querydisplayinfo->out.info->info2.entries[i].description, + info->info21.description, info->info21.account_name); + INT_EQUAL_QUERY(querydisplayinfo->out.info->info2.entries[i].rid, + info->info21.rid, info->info21.account_name); + INT_EQUAL_QUERY((querydisplayinfo->out.info->info2.entries[i].acct_flags & ~ACB_NORMAL), + info->info21.acct_flags, info->info21.account_name); + + if (!(querydisplayinfo->out.info->info2.entries[i].acct_flags & ACB_NORMAL)) { + torture_result(tctx, TORTURE_FAIL, "Missing ACB_NORMAL in querydisplayinfo->out.info.info2.entries[i].acct_flags on %s\n", + info->info21.account_name.string); + } + + if (!(info->info21.acct_flags & (ACB_WSTRUST | ACB_SVRTRUST))) { + torture_result(tctx, TORTURE_FAIL, "Found non-trust account %s in trust account listing: 0x%x 0x%x\n", + info->info21.account_name.string, + querydisplayinfo->out.info->info2.entries[i].acct_flags, + info->info21.acct_flags); + return false; + } + + break; + } + + if (!test_samr_handle_Close(b, tctx, &user_handle)) { + return false; + } + } + return ret; +} + +static bool test_QueryDisplayInfo(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDisplayInfo r; + struct samr_QueryDomainInfo dom_info; + union samr_DomainInfo *info = NULL; + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5}; + int i; + bool seen_testuser = false; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo disp_info; + + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryDisplayInfo level %u\n", levels[i]); + + r.in.start_idx = 0; + r.out.result = STATUS_MORE_ENTRIES; + while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) { + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.in.max_entries = 2; + r.in.buf_size = (uint32_t)-1; + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &disp_info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo_r(b, tctx, &r), + "QueryDisplayInfo failed"); + if (!NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) && !NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + switch (r.in.level) { + case 1: + if (!test_each_DisplayInfo_user(b, tctx, &r, &seen_testuser)) { + ret = false; + } + r.in.start_idx += r.out.info->info1.count; + break; + case 2: + if (!test_each_DisplayInfo_user(b, tctx, &r, NULL)) { + ret = false; + } + r.in.start_idx += r.out.info->info2.count; + break; + case 3: + r.in.start_idx += r.out.info->info3.count; + break; + case 4: + r.in.start_idx += r.out.info->info4.count; + break; + case 5: + r.in.start_idx += r.out.info->info5.count; + break; + } + } + dom_info.in.domain_handle = handle; + dom_info.in.level = 2; + dom_info.out.info = &info; + + /* Check number of users returned is correct */ + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo_r(b, tctx, &dom_info), + "QueryDomainInfo failed"); + if (!NT_STATUS_IS_OK(dom_info.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u failed - %s\n", + r.in.level, nt_errstr(dom_info.out.result)); + ret = false; + break; + } + switch (r.in.level) { + case 1: + case 4: + if (info->general.num_users < r.in.start_idx) { + /* On AD deployments this numbers don't match + * since QueryDisplayInfo returns universal and + * global groups, QueryDomainInfo only global + * ones. */ + if (torture_setting_bool(tctx, "samba3", false)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo indicates that QueryDisplayInfo returned more users (%d/%d) than the domain %s is said to contain!\n", + r.in.start_idx, info->general.num_groups, + info->general.domain_name.string); + ret = false; + } + } + if (!seen_testuser) { + struct policy_handle user_handle; + if (NT_STATUS_IS_OK(test_OpenUser_byname(b, tctx, handle, TEST_ACCOUNT_NAME, &user_handle))) { + torture_result(tctx, TORTURE_FAIL, "Didn't find test user " TEST_ACCOUNT_NAME " in enumeration of %s\n", + info->general.domain_name.string); + ret = false; + test_samr_handle_Close(b, tctx, &user_handle); + } + } + break; + case 3: + case 5: + if (info->general.num_groups != r.in.start_idx) { + /* On AD deployments this numbers don't match + * since QueryDisplayInfo returns universal and + * global groups, QueryDomainInfo only global + * ones. */ + if (torture_setting_bool(tctx, "samba3", false)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo indicates that QueryDisplayInfo didn't return all (%d/%d) the groups in %s\n", + r.in.start_idx, info->general.num_groups, + info->general.domain_name.string); + ret = false; + } + } + + break; + } + + } + + return ret; +} + +static bool test_QueryDisplayInfo2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDisplayInfo2 r; + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5}; + int i; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryDisplayInfo2 level %u\n", levels[i]); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.in.start_idx = 0; + r.in.max_entries = 1000; + r.in.buf_size = (uint32_t)-1; + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo2_r(b, tctx, &r), + "QueryDisplayInfo2 failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo2 level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + +static bool test_QueryDisplayInfo3(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDisplayInfo3 r; + bool ret = true; + uint16_t levels[] = {1, 2, 3, 4, 5}; + int i; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryDisplayInfo3 level %u\n", levels[i]); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.in.start_idx = 0; + r.in.max_entries = 1000; + r.in.buf_size = (uint32_t)-1; + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo3_r(b, tctx, &r), + "QueryDisplayInfo3 failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo3 level %u failed - %s\n", + levels[i], nt_errstr(r.out.result)); + ret = false; + } + } + + return ret; +} + + +static bool test_QueryDisplayInfo_continue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDisplayInfo r; + bool ret = true; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + + torture_comment(tctx, "Testing QueryDisplayInfo continuation\n"); + + r.in.domain_handle = handle; + r.in.level = 1; + r.in.start_idx = 0; + r.in.max_entries = 1; + r.in.buf_size = (uint32_t)-1; + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &info; + + do { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo_r(b, tctx, &r), + "QueryDisplayInfo failed"); + if (NT_STATUS_IS_OK(r.out.result) && *r.out.returned_size != 0) { + if (r.out.info->info1.entries[0].idx != r.in.start_idx + 1) { + torture_result(tctx, TORTURE_FAIL, "expected idx %d but got %d\n", + r.in.start_idx + 1, + r.out.info->info1.entries[0].idx); + break; + } + } + if (!NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) && + !NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo level %u failed - %s\n", + r.in.level, nt_errstr(r.out.result)); + ret = false; + break; + } + r.in.start_idx++; + } while ((NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) || + NT_STATUS_IS_OK(r.out.result)) && + *r.out.returned_size != 0); + + return ret; +} + +static bool test_QueryDomainInfo(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDomainInfo r; + union samr_DomainInfo *info = NULL; + struct samr_SetDomainInfo s; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13}; + uint16_t set_ok[] = {1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0}; + int i; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *domain_comment = talloc_asprintf(tctx, + "Tortured by Samba4 RPC-SAMR: %s", + timestring(tctx, time(NULL))); + + s.in.domain_handle = handle; + s.in.level = 4; + s.in.info = talloc(tctx, union samr_DomainInfo); + + s.in.info->oem.oem_information.string = domain_comment; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDomainInfo_r(b, tctx, &s), + "SetDomainInfo failed"); + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetDomainInfo level %u (set comment) failed - %s\n", + s.in.level, nt_errstr(s.out.result)); + return false; + } + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryDomainInfo level %u\n", levels[i]); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo_r(b, tctx, &r), + "QueryDomainInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u failed - %s\n", + r.in.level, nt_errstr(r.out.result)); + ret = false; + continue; + } + + switch (levels[i]) { + case 2: + if (strcmp(info->general.oem_information.string, domain_comment) != 0) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned different oem_information (comment) (%s, expected %s)\n", + levels[i], info->general.oem_information.string, domain_comment); + if (!torture_setting_bool(tctx, "samba3", false)) { + ret = false; + } + } + if (!info->general.primary.string) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned no PDC name\n", + levels[i]); + ret = false; + } else if (info->general.role == SAMR_ROLE_DOMAIN_PDC) { + if (dcerpc_server_name(p) && strcasecmp_m(dcerpc_server_name(p), info->general.primary.string) != 0) { + if (torture_setting_bool(tctx, "samba3", false)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned different PDC name (%s) compared to server name (%s), despite claiming to be the PDC\n", + levels[i], info->general.primary.string, dcerpc_server_name(p)); + } + } + } + break; + case 4: + if (strcmp(info->oem.oem_information.string, domain_comment) != 0) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned different oem_information (comment) (%s, expected %s)\n", + levels[i], info->oem.oem_information.string, domain_comment); + if (!torture_setting_bool(tctx, "samba3", false)) { + ret = false; + } + } + break; + case 6: + if (!info->info6.primary.string) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned no PDC name\n", + levels[i]); + ret = false; + } + break; + case 11: + if (strcmp(info->general2.general.oem_information.string, domain_comment) != 0) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u returned different comment (%s, expected %s)\n", + levels[i], info->general2.general.oem_information.string, domain_comment); + if (!torture_setting_bool(tctx, "samba3", false)) { + ret = false; + } + } + break; + } + + torture_comment(tctx, "Testing SetDomainInfo level %u\n", levels[i]); + + s.in.domain_handle = handle; + s.in.level = levels[i]; + s.in.info = info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetDomainInfo_r(b, tctx, &s), + "SetDomainInfo failed"); + if (set_ok[i]) { + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetDomainInfo level %u failed - %s\n", + r.in.level, nt_errstr(s.out.result)); + ret = false; + continue; + } + } else { + if (!NT_STATUS_EQUAL(NT_STATUS_INVALID_INFO_CLASS, s.out.result)) { + torture_result(tctx, TORTURE_FAIL, "SetDomainInfo level %u gave %s - should have been NT_STATUS_INVALID_INFO_CLASS\n", + r.in.level, nt_errstr(s.out.result)); + ret = false; + continue; + } + } + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo_r(b, tctx, &r), + "QueryDomainInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo level %u failed - %s\n", + r.in.level, nt_errstr(r.out.result)); + ret = false; + continue; + } + } + + return ret; +} + + +static bool test_QueryDomainInfo2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_QueryDomainInfo2 r; + union samr_DomainInfo *info = NULL; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13}; + int i; + bool ret = true; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + torture_comment(tctx, "Testing QueryDomainInfo2 level %u\n", levels[i]); + + r.in.domain_handle = handle; + r.in.level = levels[i]; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo2_r(b, tctx, &r), + "QueryDomainInfo2 failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "QueryDomainInfo2 level %u failed - %s\n", + r.in.level, nt_errstr(r.out.result)); + ret = false; + continue; + } + } + + return ret; +} + +/* Test whether querydispinfo level 5 and enumdomgroups return the same + set of group names. */ +static bool test_GroupList(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct dom_sid *domain_sid, + struct policy_handle *handle) +{ + struct samr_EnumDomainGroups q1; + struct samr_QueryDisplayInfo q2; + NTSTATUS status; + uint32_t resume_handle=0; + struct samr_SamArray *sam = NULL; + uint32_t num_entries = 0; + int i; + bool ret = true; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + + size_t num_names = 0; + const char **names = NULL; + + bool builtin_domain = dom_sid_compare(domain_sid, + &global_sid_Builtin) == 0; + + torture_comment(tctx, "Testing coherency of querydispinfo vs enumdomgroups\n"); + + q1.in.domain_handle = handle; + q1.in.resume_handle = &resume_handle; + q1.in.max_size = 5; + q1.out.resume_handle = &resume_handle; + q1.out.num_entries = &num_entries; + q1.out.sam = &sam; + + status = STATUS_MORE_ENTRIES; + while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainGroups_r(b, tctx, &q1), + "EnumDomainGroups failed"); + status = q1.out.result; + + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) + break; + + for (i=0; i<*q1.out.num_entries; i++) { + add_string_to_array(tctx, + sam->entries[i].name.string, + &names, &num_names); + } + } + + torture_assert_ntstatus_ok(tctx, status, "EnumDomainGroups"); + + torture_assert(tctx, sam, "EnumDomainGroups failed to return sam"); + + if (builtin_domain) { + torture_assert(tctx, num_names == 0, + "EnumDomainGroups shouldn't return any group in the builtin domain!"); + } + + q2.in.domain_handle = handle; + q2.in.level = 5; + q2.in.start_idx = 0; + q2.in.max_entries = 5; + q2.in.buf_size = (uint32_t)-1; + q2.out.total_size = &total_size; + q2.out.returned_size = &returned_size; + q2.out.info = &info; + + status = STATUS_MORE_ENTRIES; + while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo_r(b, tctx, &q2), + "QueryDisplayInfo failed"); + status = q2.out.result; + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) + break; + + for (i=0; i<q2.out.info->info5.count; i++) { + int j; + const char *name = q2.out.info->info5.entries[i].account_name.string; + bool found = false; + for (j=0; j<num_names; j++) { + if (names[j] == NULL) + continue; + if (strequal(names[j], name)) { + names[j] = NULL; + found = true; + break; + } + } + + if ((!found) && (!builtin_domain)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo gave name [%s] that EnumDomainGroups did not\n", + name); + ret = false; + } + } + q2.in.start_idx += q2.out.info->info5.count; + } + + if (!NT_STATUS_IS_OK(status)) { + torture_result(tctx, TORTURE_FAIL, "QueryDisplayInfo level 5 failed - %s\n", + nt_errstr(status)); + ret = false; + } + + if (builtin_domain) { + torture_assert(tctx, q2.in.start_idx != 0, + "QueryDisplayInfo should return all domain groups also on the builtin domain handle!"); + } + + for (i=0; i<num_names; i++) { + if (names[i] != NULL) { + torture_result(tctx, TORTURE_FAIL, "EnumDomainGroups gave name [%s] that QueryDisplayInfo did not\n", + names[i]); + ret = false; + } + } + + return ret; +} + +static bool test_DeleteDomainGroup(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *group_handle) +{ + struct samr_DeleteDomainGroup d; + + torture_comment(tctx, "Testing DeleteDomainGroup\n"); + + d.in.group_handle = group_handle; + d.out.group_handle = group_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteDomainGroup_r(b, tctx, &d), + "DeleteDomainGroup failed"); + torture_assert_ntstatus_ok(tctx, d.out.result, "DeleteDomainGroup"); + + return true; +} + +static bool test_TestPrivateFunctionsDomain(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle) +{ + struct samr_TestPrivateFunctionsDomain r; + bool ret = true; + + torture_comment(tctx, "Testing TestPrivateFunctionsDomain\n"); + + r.in.domain_handle = domain_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_TestPrivateFunctionsDomain_r(b, tctx, &r), + "TestPrivateFunctionsDomain failed"); + torture_assert_ntstatus_equal(tctx, r.out.result, NT_STATUS_NOT_IMPLEMENTED, "TestPrivateFunctionsDomain"); + + return ret; +} + +static bool test_RidToSid(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct dom_sid *domain_sid, + struct policy_handle *domain_handle) +{ + struct samr_RidToSid r; + bool ret = true; + struct dom_sid *calc_sid, *out_sid; + int rids[] = { 0, 42, 512, 10200 }; + int i; + + for (i=0;i<ARRAY_SIZE(rids);i++) { + torture_comment(tctx, "Testing RidToSid\n"); + + calc_sid = dom_sid_dup(tctx, domain_sid); + r.in.domain_handle = domain_handle; + r.in.rid = rids[i]; + r.out.sid = &out_sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_RidToSid_r(b, tctx, &r), + "RidToSid failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_result(tctx, TORTURE_FAIL, "RidToSid for %d failed - %s\n", rids[i], nt_errstr(r.out.result)); + ret = false; + } else { + calc_sid = dom_sid_add_rid(calc_sid, calc_sid, rids[i]); + + if (!dom_sid_equal(calc_sid, out_sid)) { + torture_result(tctx, TORTURE_FAIL, "RidToSid for %d failed - got %s, expected %s\n", rids[i], + dom_sid_string(tctx, out_sid), + dom_sid_string(tctx, calc_sid)); + ret = false; + } + } + } + + return ret; +} + +static bool test_GetBootKeyInformation(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle) +{ + struct samr_GetBootKeyInformation r; + bool ret = true; + uint32_t unknown = 0; + NTSTATUS status; + + torture_comment(tctx, "Testing GetBootKeyInformation\n"); + + r.in.domain_handle = domain_handle; + r.out.unknown = &unknown; + + status = dcerpc_samr_GetBootKeyInformation_r(b, tctx, &r); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(r.out.result)) { + status = r.out.result; + } + if (!NT_STATUS_IS_OK(status)) { + /* w2k3 seems to fail this sometimes and pass it sometimes */ + torture_comment(tctx, "GetBootKeyInformation (ignored) - %s\n", nt_errstr(status)); + } + + return ret; +} + +static bool test_AddGroupMember(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + struct policy_handle *group_handle) +{ + NTSTATUS status; + struct samr_AddGroupMember r; + struct samr_DeleteGroupMember d; + struct samr_QueryGroupMember q; + struct samr_RidAttrArray *rids = NULL; + struct samr_SetMemberAttributesOfGroup s; + uint32_t rid; + bool found_member = false; + int i; + + status = test_LookupName(b, tctx, domain_handle, TEST_ACCOUNT_NAME, &rid); + torture_assert_ntstatus_ok(tctx, status, "test_AddGroupMember looking up name " TEST_ACCOUNT_NAME); + + r.in.group_handle = group_handle; + r.in.rid = rid; + r.in.flags = 0; /* ??? */ + + torture_comment(tctx, "Testing AddGroupMember, QueryGroupMember and DeleteGroupMember\n"); + + d.in.group_handle = group_handle; + d.in.rid = rid; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteGroupMember_r(b, tctx, &d), + "DeleteGroupMember failed"); + torture_assert_ntstatus_equal(tctx, NT_STATUS_MEMBER_NOT_IN_GROUP, d.out.result, "DeleteGroupMember"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_AddGroupMember_r(b, tctx, &r), + "AddGroupMember failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "AddGroupMember"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_AddGroupMember_r(b, tctx, &r), + "AddGroupMember failed"); + torture_assert_ntstatus_equal(tctx, NT_STATUS_MEMBER_IN_GROUP, r.out.result, "AddGroupMember"); + + if (torture_setting_bool(tctx, "samba4", false) || + torture_setting_bool(tctx, "samba3", false)) { + torture_comment(tctx, "skipping SetMemberAttributesOfGroup test against Samba\n"); + } else { + /* this one is quite strange. I am using random inputs in the + hope of triggering an error that might give us a clue */ + + s.in.group_handle = group_handle; + s.in.unknown1 = random(); + s.in.unknown2 = random(); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetMemberAttributesOfGroup_r(b, tctx, &s), + "SetMemberAttributesOfGroup failed"); + torture_assert_ntstatus_ok(tctx, s.out.result, "SetMemberAttributesOfGroup"); + } + + q.in.group_handle = group_handle; + q.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupMember_r(b, tctx, &q), + "QueryGroupMember failed"); + torture_assert_ntstatus_ok(tctx, q.out.result, "QueryGroupMember"); + torture_assert(tctx, rids, "QueryGroupMember did not fill in rids structure"); + + for (i=0; i < rids->count; i++) { + if (rids->rids[i] == rid) { + found_member = true; + } + } + + torture_assert(tctx, found_member, "QueryGroupMember did not list newly added member"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_DeleteGroupMember_r(b, tctx, &d), + "DeleteGroupMember failed"); + torture_assert_ntstatus_ok(tctx, d.out.result, "DeleteGroupMember"); + + rids = NULL; + found_member = false; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupMember_r(b, tctx, &q), + "QueryGroupMember failed"); + torture_assert_ntstatus_ok(tctx, q.out.result, "QueryGroupMember"); + torture_assert(tctx, rids, "QueryGroupMember did not fill in rids structure"); + + for (i=0; i < rids->count; i++) { + if (rids->rids[i] == rid) { + found_member = true; + } + } + + torture_assert(tctx, !found_member, "QueryGroupMember does still list removed member"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_AddGroupMember_r(b, tctx, &r), + "AddGroupMember failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "AddGroupMember"); + + return true; +} + + +static bool test_CreateDomainGroup(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *group_name, + struct policy_handle *group_handle, + struct dom_sid *domain_sid, + bool test_group) +{ + struct samr_CreateDomainGroup r; + uint32_t rid; + struct lsa_String name; + bool ret = true; + + init_lsa_String(&name, group_name); + + r.in.domain_handle = domain_handle; + r.in.name = &name; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.group_handle = group_handle; + r.out.rid = &rid; + + torture_comment(tctx, "Testing CreateDomainGroup(%s)\n", r.in.name->string); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateDomainGroup_r(b, tctx, &r), + "CreateDomainGroup failed"); + + if (dom_sid_equal(domain_sid, dom_sid_parse_talloc(tctx, SID_BUILTIN))) { + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, "Server correctly refused create of '%s'\n", r.in.name->string); + return true; + } else { + torture_result(tctx, TORTURE_FAIL, "Server should have refused create of '%s', got %s instead\n", r.in.name->string, + nt_errstr(r.out.result)); + return false; + } + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_GROUP_EXISTS)) { + if (!test_DeleteGroup_byname(b, tctx, domain_handle, r.in.name->string)) { + torture_result(tctx, TORTURE_FAIL, "CreateDomainGroup failed: Could not delete domain group %s - %s\n", r.in.name->string, + nt_errstr(r.out.result)); + return false; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateDomainGroup_r(b, tctx, &r), + "CreateDomainGroup failed"); + } + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_USER_EXISTS)) { + if (!test_DeleteUser_byname(b, tctx, domain_handle, r.in.name->string)) { + + torture_result(tctx, TORTURE_FAIL, "CreateDomainGroup failed: Could not delete user %s - %s\n", r.in.name->string, + nt_errstr(r.out.result)); + return false; + } + torture_assert_ntstatus_ok(tctx, dcerpc_samr_CreateDomainGroup_r(b, tctx, &r), + "CreateDomainGroup failed"); + } + torture_assert_ntstatus_ok(tctx, r.out.result, "CreateDomainGroup"); + + if (!test_group) { + return ret; + } + + if (!test_AddGroupMember(b, tctx, domain_handle, group_handle)) { + torture_result(tctx, TORTURE_FAIL, "CreateDomainGroup failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + if (!test_SetGroupInfo(b, tctx, group_handle)) { + ret = false; + } + + return ret; +} + + +/* + its not totally clear what this does. It seems to accept any sid you like. +*/ +static bool test_RemoveMemberFromForeignDomain(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle) +{ + struct samr_RemoveMemberFromForeignDomain r; + + r.in.domain_handle = domain_handle; + r.in.sid = dom_sid_parse_talloc(tctx, "S-1-5-32-12-34-56-78"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_RemoveMemberFromForeignDomain_r(b, tctx, &r), + "RemoveMemberFromForeignDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "RemoveMemberFromForeignDomain"); + + return true; +} + +static bool test_EnumDomainUsers(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + uint32_t *total_num_entries_p) +{ + NTSTATUS status; + struct samr_EnumDomainUsers r; + uint32_t resume_handle = 0; + uint32_t num_entries = 0; + uint32_t total_num_entries = 0; + struct samr_SamArray *sam; + + r.in.domain_handle = domain_handle; + r.in.acct_flags = 0; + r.in.max_size = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + torture_comment(tctx, "Testing EnumDomainUsers\n"); + + do { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainUsers_r(b, tctx, &r), + "EnumDomainUsers failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to enumerate users"); + } + status = r.out.result; + + total_num_entries += num_entries; + } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)); + + if (total_num_entries_p) { + *total_num_entries_p = total_num_entries; + } + + return true; +} + +static bool test_EnumDomainGroups(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + uint32_t *total_num_entries_p) +{ + NTSTATUS status; + struct samr_EnumDomainGroups r; + uint32_t resume_handle = 0; + uint32_t num_entries = 0; + uint32_t total_num_entries = 0; + struct samr_SamArray *sam; + + r.in.domain_handle = domain_handle; + r.in.max_size = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + torture_comment(tctx, "Testing EnumDomainGroups\n"); + + do { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainGroups_r(b, tctx, &r), + "EnumDomainGroups failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to enumerate groups"); + } + status = r.out.result; + + total_num_entries += num_entries; + } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)); + + if (total_num_entries_p) { + *total_num_entries_p = total_num_entries; + } + + return true; +} + +static bool test_EnumDomainAliases(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + uint32_t *total_num_entries_p) +{ + NTSTATUS status; + struct samr_EnumDomainAliases r; + uint32_t resume_handle = 0; + uint32_t num_entries = 0; + uint32_t total_num_entries = 0; + struct samr_SamArray *sam; + + r.in.domain_handle = domain_handle; + r.in.max_size = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + torture_comment(tctx, "Testing EnumDomainAliases\n"); + + do { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainAliases_r(b, tctx, &r), + "EnumDomainAliases failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to enumerate aliases"); + } + status = r.out.result; + + total_num_entries += num_entries; + } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)); + + if (total_num_entries_p) { + *total_num_entries_p = total_num_entries; + } + + return true; +} + +static bool test_QueryDisplayInfo_level(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + uint16_t level, + uint32_t *total_num_entries_p) +{ + NTSTATUS status; + struct samr_QueryDisplayInfo r; + uint32_t total_num_entries = 0; + + r.in.domain_handle = handle; + r.in.level = level; + r.in.start_idx = 0; + r.in.max_entries = (uint32_t)-1; + r.in.buf_size = (uint32_t)-1; + + torture_comment(tctx, "Testing QueryDisplayInfo\n"); + + do { + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo_r(b, tctx, &r), + "failed to query displayinfo"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query displayinfo"); + } + status = r.out.result; + + if (*r.out.returned_size == 0) { + break; + } + + switch (r.in.level) { + case 1: + total_num_entries += info.info1.count; + r.in.start_idx += info.info1.entries[info.info1.count - 1].idx + 1; + break; + case 2: + total_num_entries += info.info2.count; + r.in.start_idx += info.info2.entries[info.info2.count - 1].idx + 1; + break; + case 3: + total_num_entries += info.info3.count; + r.in.start_idx += info.info3.entries[info.info3.count - 1].idx + 1; + break; + case 4: + total_num_entries += info.info4.count; + r.in.start_idx += info.info4.entries[info.info4.count - 1].idx + 1; + break; + case 5: + total_num_entries += info.info5.count; + r.in.start_idx += info.info5.entries[info.info5.count - 1].idx + 1; + break; + default: + return false; + } + + } while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)); + + if (total_num_entries_p) { + *total_num_entries_p = total_num_entries; + } + + return true; +} + +static bool test_ManyObjects(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *domain_handle, + struct dom_sid *domain_sid, + struct torture_samr_context *ctx) +{ + uint32_t num_total = ctx->num_objects_large_dc; + uint32_t num_enum = 0; + uint32_t num_disp = 0; + uint32_t num_created = 0; + uint32_t num_anounced = 0; + uint32_t i; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct policy_handle *handles = talloc_zero_array(tctx, struct policy_handle, num_total); + + /* query */ + + { + struct samr_QueryDomainInfo2 r; + union samr_DomainInfo *info; + r.in.domain_handle = domain_handle; + r.in.level = 2; + r.out.info = &info; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo2_r(b, tctx, &r), + "QueryDomainInfo2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query domain info"); + + switch (ctx->choice) { + case TORTURE_SAMR_MANY_ACCOUNTS: + num_anounced = info->general.num_users; + break; + case TORTURE_SAMR_MANY_GROUPS: + num_anounced = info->general.num_groups; + break; + case TORTURE_SAMR_MANY_ALIASES: + num_anounced = info->general.num_aliases; + break; + default: + return false; + } + } + + /* create */ + + for (i=0; i < num_total; i++) { + + const char *name = NULL; + + switch (ctx->choice) { + case TORTURE_SAMR_MANY_ACCOUNTS: + name = talloc_asprintf(tctx, "%s%04d", TEST_ACCOUNT_NAME, i); + torture_assert(tctx, + test_CreateUser(p, tctx, domain_handle, name, &handles[i], domain_sid, 0, NULL, false), + "failed to create user"); + break; + case TORTURE_SAMR_MANY_GROUPS: + name = talloc_asprintf(tctx, "%s%04d", TEST_GROUPNAME, i); + torture_assert(tctx, + test_CreateDomainGroup(b, tctx, domain_handle, name, &handles[i], domain_sid, false), + "failed to create group"); + break; + case TORTURE_SAMR_MANY_ALIASES: + name = talloc_asprintf(tctx, "%s%04d", TEST_ALIASNAME, i); + torture_assert(tctx, + test_CreateAlias(b, tctx, domain_handle, name, &handles[i], domain_sid, false), + "failed to create alias"); + break; + default: + return false; + } + if (!ndr_policy_handle_empty(&handles[i])) { + num_created++; + } + } + + /* enum */ + + switch (ctx->choice) { + case TORTURE_SAMR_MANY_ACCOUNTS: + torture_assert(tctx, + test_EnumDomainUsers(b, tctx, domain_handle, &num_enum), + "failed to enum users"); + break; + case TORTURE_SAMR_MANY_GROUPS: + torture_assert(tctx, + test_EnumDomainGroups(b, tctx, domain_handle, &num_enum), + "failed to enum groups"); + break; + case TORTURE_SAMR_MANY_ALIASES: + torture_assert(tctx, + test_EnumDomainAliases(b, tctx, domain_handle, &num_enum), + "failed to enum aliases"); + break; + default: + return false; + } + + /* dispinfo */ + + switch (ctx->choice) { + case TORTURE_SAMR_MANY_ACCOUNTS: + torture_assert(tctx, + test_QueryDisplayInfo_level(b, tctx, domain_handle, 1, &num_disp), + "failed to query display info"); + break; + case TORTURE_SAMR_MANY_GROUPS: + torture_assert(tctx, + test_QueryDisplayInfo_level(b, tctx, domain_handle, 3, &num_disp), + "failed to query display info"); + break; + case TORTURE_SAMR_MANY_ALIASES: + /* no aliases in dispinfo */ + break; + default: + return false; + } + + /* close or delete */ + + for (i=0; i < num_total; i++) { + + if (ndr_policy_handle_empty(&handles[i])) { + continue; + } + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_assert(tctx, + test_samr_handle_Close(b, tctx, &handles[i]), + "failed to close handle"); + } else { + switch (ctx->choice) { + case TORTURE_SAMR_MANY_ACCOUNTS: + torture_assert(tctx, + test_DeleteUser(b, tctx, &handles[i]), + "failed to delete user"); + break; + case TORTURE_SAMR_MANY_GROUPS: + torture_assert(tctx, + test_DeleteDomainGroup(b, tctx, &handles[i]), + "failed to delete group"); + break; + case TORTURE_SAMR_MANY_ALIASES: + torture_assert(tctx, + test_DeleteAlias(b, tctx, &handles[i]), + "failed to delete alias"); + break; + default: + return false; + } + } + } + + talloc_free(handles); + + if (ctx->choice == TORTURE_SAMR_MANY_ACCOUNTS && num_enum != num_anounced + num_created) { + torture_comment(tctx, + "unexpected number of results (%u) returned in enum call, expected %u\n", + num_enum, num_anounced + num_created); + + torture_comment(tctx, + "unexpected number of results (%u) returned in dispinfo, call, expected %u\n", + num_disp, num_anounced + num_created); + } + + return true; +} + +static bool test_Connect(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle); + +static bool test_OpenDomain(struct dcerpc_pipe *p, struct torture_context *tctx, + struct torture_samr_context *ctx, struct dom_sid *sid) +{ + struct samr_OpenDomain r; + struct policy_handle domain_handle; + struct policy_handle alias_handle; + struct policy_handle user_handle; + struct policy_handle group_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(alias_handle); + ZERO_STRUCT(user_handle); + ZERO_STRUCT(group_handle); + ZERO_STRUCT(domain_handle); + + torture_comment(tctx, "Testing OpenDomain of %s\n", dom_sid_string(tctx, sid)); + + r.in.connect_handle = &ctx->handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.sid = sid; + r.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenDomain_r(b, tctx, &r), + "OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "OpenDomain failed"); + + /* run the domain tests with the main handle closed - this tests + the servers reference counting */ + torture_assert(tctx, test_samr_handle_Close(b, tctx, &ctx->handle), "Failed to close SAMR handle"); + + switch (ctx->choice) { + case TORTURE_SAMR_PASSWORDS: + case TORTURE_SAMR_USER_PRIVILEGES: + if (!torture_setting_bool(tctx, "samba3", false)) { + ret &= test_CreateUser2(p, tctx, &domain_handle, sid, ctx->choice, NULL); + } + ret &= test_CreateUser(p, tctx, &domain_handle, TEST_ACCOUNT_NAME, &user_handle, sid, ctx->choice, NULL, true); + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Testing PASSWORDS or PRIVILEGES on domain %s failed!\n", dom_sid_string(tctx, sid)); + } + break; + case TORTURE_SAMR_USER_ATTRIBUTES: + if (!torture_setting_bool(tctx, "samba3", false)) { + ret &= test_CreateUser2(p, tctx, &domain_handle, sid, ctx->choice, NULL); + } + ret &= test_CreateUser(p, tctx, &domain_handle, TEST_ACCOUNT_NAME, &user_handle, sid, ctx->choice, NULL, true); + /* This test needs 'complex' users to validate */ + ret &= test_QueryDisplayInfo(b, tctx, &domain_handle); + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Testing ATTRIBUTES on domain %s failed!\n", dom_sid_string(tctx, sid)); + } + break; + case TORTURE_SAMR_PASSWORDS_PWDLASTSET: + case TORTURE_SAMR_PASSWORDS_BADPWDCOUNT: + case TORTURE_SAMR_PASSWORDS_LOCKOUT: + if (!torture_setting_bool(tctx, "samba3", false)) { + ret &= test_CreateUser2(p, tctx, &domain_handle, sid, ctx->choice, ctx->machine_credentials); + } + ret &= test_CreateUser(p, tctx, &domain_handle, TEST_ACCOUNT_NAME, &user_handle, sid, ctx->choice, ctx->machine_credentials, true); + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Testing PASSWORDS PWDLASTSET or BADPWDCOUNT on domain %s failed!\n", dom_sid_string(tctx, sid)); + } + break; + case TORTURE_SAMR_MANY_ACCOUNTS: + case TORTURE_SAMR_MANY_GROUPS: + case TORTURE_SAMR_MANY_ALIASES: + ret &= test_ManyObjects(p, tctx, &domain_handle, sid, ctx); + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Testing MANY-{ACCOUNTS,GROUPS,ALIASES} on domain %s failed!\n", dom_sid_string(tctx, sid)); + } + break; + case TORTURE_SAMR_OTHER: + ret &= test_CreateUser(p, tctx, &domain_handle, TEST_ACCOUNT_NAME, &user_handle, sid, ctx->choice, NULL, true); + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Failed to CreateUser in SAMR-OTHER on domain %s!\n", dom_sid_string(tctx, sid)); + } + if (!torture_setting_bool(tctx, "samba3", false)) { + ret &= test_QuerySecurity(b, tctx, &domain_handle); + } + ret &= test_RemoveMemberFromForeignDomain(b, tctx, &domain_handle); + ret &= test_CreateAlias(b, tctx, &domain_handle, TEST_ALIASNAME, &alias_handle, sid, true); + ret &= test_CreateDomainGroup(b, tctx, &domain_handle, TEST_GROUPNAME, &group_handle, sid, true); + ret &= test_GetAliasMembership(b, tctx, &domain_handle); + ret &= test_QueryDomainInfo(p, tctx, &domain_handle); + ret &= test_QueryDomainInfo2(b, tctx, &domain_handle); + ret &= test_EnumDomainUsers_all(b, tctx, &domain_handle); + ret &= test_EnumDomainUsers_async(p, tctx, &domain_handle); + ret &= test_EnumDomainGroups_all(b, tctx, &domain_handle); + ret &= test_EnumDomainAliases_all(b, tctx, &domain_handle); + ret &= test_QueryDisplayInfo2(b, tctx, &domain_handle); + ret &= test_QueryDisplayInfo3(b, tctx, &domain_handle); + ret &= test_QueryDisplayInfo_continue(b, tctx, &domain_handle); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping GetDisplayEnumerationIndex test against Samba4\n"); + } else { + ret &= test_GetDisplayEnumerationIndex(b, tctx, &domain_handle); + ret &= test_GetDisplayEnumerationIndex2(b, tctx, &domain_handle); + } + ret &= test_GroupList(b, tctx, sid, &domain_handle); + ret &= test_TestPrivateFunctionsDomain(b, tctx, &domain_handle); + ret &= test_RidToSid(b, tctx, sid, &domain_handle); + ret &= test_GetBootKeyInformation(b, tctx, &domain_handle); + if (!ret) { + torture_comment(tctx, "Testing SAMR-OTHER on domain %s failed!\n", dom_sid_string(tctx, sid)); + } + break; + } + + if (!ndr_policy_handle_empty(&user_handle) && + !test_DeleteUser(b, tctx, &user_handle)) { + ret = false; + } + + if (!ndr_policy_handle_empty(&alias_handle) && + !test_DeleteAlias(b, tctx, &alias_handle)) { + ret = false; + } + + if (!ndr_policy_handle_empty(&group_handle) && + !test_DeleteDomainGroup(b, tctx, &group_handle)) { + ret = false; + } + + torture_assert(tctx, test_samr_handle_Close(b, tctx, &domain_handle), "Failed to close SAMR domain handle"); + + torture_assert(tctx, test_Connect(b, tctx, &ctx->handle), "Faile to re-connect SAMR handle"); + /* reconnect the main handle */ + + if (!ret) { + torture_result(tctx, TORTURE_FAIL, "Testing domain %s failed!\n", dom_sid_string(tctx, sid)); + } + + return ret; +} + +static bool test_LookupDomain(struct dcerpc_pipe *p, struct torture_context *tctx, + struct torture_samr_context *ctx, const char *domain) +{ + struct samr_LookupDomain r; + struct dom_sid2 *sid = NULL; + struct lsa_String n1; + struct lsa_String n2; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing LookupDomain(%s)\n", domain); + + /* check for correct error codes */ + r.in.connect_handle = &ctx->handle; + r.in.domain_name = &n2; + r.out.sid = &sid; + n2.string = NULL; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &r), + "LookupDomain failed"); + torture_assert_ntstatus_equal(tctx, NT_STATUS_INVALID_PARAMETER, r.out.result, "LookupDomain expected NT_STATUS_INVALID_PARAMETER"); + + init_lsa_String(&n2, "xxNODOMAINxx"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &r), + "LookupDomain failed"); + torture_assert_ntstatus_equal(tctx, NT_STATUS_NO_SUCH_DOMAIN, r.out.result, "LookupDomain expected NT_STATUS_NO_SUCH_DOMAIN"); + + r.in.connect_handle = &ctx->handle; + + init_lsa_String(&n1, domain); + r.in.domain_name = &n1; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &r), + "LookupDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LookupDomain"); + + if (!test_GetDomPwInfo(p, tctx, &n1)) { + ret = false; + } + + if (!test_OpenDomain(p, tctx, ctx, *r.out.sid)) { + ret = false; + } + + return ret; +} + + +static bool test_EnumDomains(struct dcerpc_pipe *p, struct torture_context *tctx, + struct torture_samr_context *ctx) +{ + struct samr_EnumDomains r; + uint32_t resume_handle = 0; + uint32_t num_entries = 0; + struct samr_SamArray *sam = NULL; + int i; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.connect_handle = &ctx->handle; + r.in.resume_handle = &resume_handle; + r.in.buf_size = (uint32_t)-1; + r.out.resume_handle = &resume_handle; + r.out.num_entries = &num_entries; + r.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomains_r(b, tctx, &r), + "EnumDomains failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "EnumDomains failed"); + + if (!*r.out.sam) { + return false; + } + + for (i=0;i<sam->count;i++) { + if (!test_LookupDomain(p, tctx, ctx, + sam->entries[i].name.string)) { + ret = false; + } + } + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomains_r(b, tctx, &r), + "EnumDomains failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "EnumDomains failed"); + + return ret; +} + + +static bool test_Connect(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct samr_Connect r; + struct samr_Connect2 r2; + struct samr_Connect3 r3; + struct samr_Connect4 r4; + struct samr_Connect5 r5; + union samr_ConnectInfo info; + struct policy_handle h; + uint32_t level_out = 0; + bool ret = true, got_handle = false; + + torture_comment(tctx, "Testing samr_Connect\n"); + + r.in.system_name = NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.connect_handle = &h; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect_r(b, tctx, &r), + "Connect failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "Connect failed - %s\n", nt_errstr(r.out.result)); + ret = false; + } else { + got_handle = true; + *handle = h; + } + + torture_comment(tctx, "Testing samr_Connect2\n"); + + r2.in.system_name = NULL; + r2.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r2.out.connect_handle = &h; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect2_r(b, tctx, &r2), + "Connect2 failed"); + if (!NT_STATUS_IS_OK(r2.out.result)) { + torture_comment(tctx, "Connect2 failed - %s\n", nt_errstr(r2.out.result)); + ret = false; + } else { + if (got_handle) { + test_samr_handle_Close(b, tctx, handle); + } + got_handle = true; + *handle = h; + } + + torture_comment(tctx, "Testing samr_Connect3\n"); + + r3.in.system_name = NULL; + r3.in.unknown = 0; + r3.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r3.out.connect_handle = &h; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect3_r(b, tctx, &r3), + "Connect3 failed"); + if (!NT_STATUS_IS_OK(r3.out.result)) { + torture_result(tctx, TORTURE_FAIL, "Connect3 failed - %s\n", nt_errstr(r3.out.result)); + ret = false; + } else { + if (got_handle) { + test_samr_handle_Close(b, tctx, handle); + } + got_handle = true; + *handle = h; + } + + torture_comment(tctx, "Testing samr_Connect4\n"); + + r4.in.system_name = ""; + r4.in.client_version = 0; + r4.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r4.out.connect_handle = &h; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect4_r(b, tctx, &r4), + "Connect4 failed"); + if (!NT_STATUS_IS_OK(r4.out.result)) { + torture_result(tctx, TORTURE_FAIL, "Connect4 failed - %s\n", nt_errstr(r4.out.result)); + ret = false; + } else { + if (got_handle) { + test_samr_handle_Close(b, tctx, handle); + } + got_handle = true; + *handle = h; + } + + torture_comment(tctx, "Testing samr_Connect5\n"); + + info.info1.client_version = 0; + info.info1.supported_features = 0; + + r5.in.system_name = ""; + r5.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r5.in.level_in = 1; + r5.out.level_out = &level_out; + r5.in.info_in = &info; + r5.out.info_out = &info; + r5.out.connect_handle = &h; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect5_r(b, tctx, &r5), + "Connect5 failed"); + if (!NT_STATUS_IS_OK(r5.out.result)) { + torture_result(tctx, TORTURE_FAIL, "Connect5 failed - %s\n", nt_errstr(r5.out.result)); + ret = false; + } else { + if (got_handle) { + test_samr_handle_Close(b, tctx, handle); + } + got_handle = true; + *handle = h; + } + + return ret; +} + + +static bool test_samr_ValidatePassword(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct samr_ValidatePassword r; + union samr_ValidatePasswordReq req; + union samr_ValidatePasswordRep *repp = NULL; + NTSTATUS status; + const char *passwords[] = { "penguin", "p@ssw0rd", "p@ssw0rd123$", NULL }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing samr_ValidatePassword\n"); + + if (p->conn->transport.transport != NCACN_IP_TCP) { + torture_comment(tctx, "samr_ValidatePassword only should succeed over NCACN_IP_TCP!\n"); + } + + ZERO_STRUCT(r); + r.in.level = NetValidatePasswordReset; + r.in.req = &req; + r.out.rep = &repp; + + ZERO_STRUCT(req); + req.req3.account.string = "non-existent-account-aklsdji"; + + for (i=0; passwords[i]; i++) { + req.req3.password.string = passwords[i]; + + status = dcerpc_samr_ValidatePassword_r(b, tctx, &r); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + torture_skip(tctx, "ValidatePassword not supported by server\n"); + } + torture_assert_ntstatus_ok(tctx, status, + "samr_ValidatePassword failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_ValidatePassword failed"); + torture_comment(tctx, "Server %s password '%s' with code %i\n", + repp->ctr3.status==SAMR_VALIDATION_STATUS_SUCCESS?"allowed":"refused", + req.req3.password.string, repp->ctr3.status); + } + + return true; +} + +bool torture_rpc_samr(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_OTHER; + + ret &= test_Connect(b, torture, &ctx->handle); + + if (!torture_setting_bool(torture, "samba3", false)) { + ret &= test_QuerySecurity(b, torture, &ctx->handle); + } + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_SetDsrmPassword(b, torture, &ctx->handle); + + ret &= test_Shutdown(b, torture, &ctx->handle); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + + +bool torture_rpc_samr_users(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_USER_ATTRIBUTES; + + ret &= test_Connect(b, torture, &ctx->handle); + + if (!torture_setting_bool(torture, "samba3", false)) { + ret &= test_QuerySecurity(b, torture, &ctx->handle); + } + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_SetDsrmPassword(b, torture, &ctx->handle); + + ret &= test_Shutdown(b, torture, &ctx->handle); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + + +bool torture_rpc_samr_passwords(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_PASSWORDS; + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +static bool torture_rpc_samr_pwdlastset(struct torture_context *torture, + struct dcerpc_pipe *p2, + struct cli_credentials *machine_credentials) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_PASSWORDS_PWDLASTSET; + ctx->machine_credentials = machine_credentials; + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +struct torture_suite *torture_rpc_samr_passwords_pwdlastset(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.passwords.pwdlastset"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "samr", + &ndr_table_samr, + TEST_ACCOUNT_NAME_PWD); + + torture_rpc_tcase_add_test_creds(tcase, "pwdLastSet", + torture_rpc_samr_pwdlastset); + + return suite; +} + +static bool torture_rpc_samr_users_privileges_delete_user(struct torture_context *torture, + struct dcerpc_pipe *p2, + struct cli_credentials *machine_credentials) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_USER_PRIVILEGES; + ctx->machine_credentials = machine_credentials; + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +struct torture_suite *torture_rpc_samr_user_privileges(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.users.privileges"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "samr", + &ndr_table_samr, + TEST_ACCOUNT_NAME_PWD); + + torture_rpc_tcase_add_test_creds(tcase, "delete_privileged_user", + torture_rpc_samr_users_privileges_delete_user); + + return suite; +} + +static bool torture_rpc_samr_many_accounts(struct torture_context *torture, + struct dcerpc_pipe *p2, + void *data) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx = + talloc_get_type_abort(data, struct torture_samr_context); + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx->choice = TORTURE_SAMR_MANY_ACCOUNTS; + ctx->num_objects_large_dc = torture_setting_int(torture, "large_dc", + ctx->num_objects_large_dc); + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +static bool torture_rpc_samr_many_groups(struct torture_context *torture, + struct dcerpc_pipe *p2, + void *data) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx = + talloc_get_type_abort(data, struct torture_samr_context); + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx->choice = TORTURE_SAMR_MANY_GROUPS; + ctx->num_objects_large_dc = torture_setting_int(torture, "large_dc", + ctx->num_objects_large_dc); + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +static bool torture_rpc_samr_many_aliases(struct torture_context *torture, + struct dcerpc_pipe *p2, + void *data) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx = + talloc_get_type_abort(data, struct torture_samr_context); + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx->choice = TORTURE_SAMR_MANY_ALIASES; + ctx->num_objects_large_dc = torture_setting_int(torture, "large_dc", + ctx->num_objects_large_dc); + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +struct torture_suite *torture_rpc_samr_large_dc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.large-dc"); + struct torture_rpc_tcase *tcase; + struct torture_samr_context *ctx; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "samr", &ndr_table_samr); + + ctx = talloc_zero(suite, struct torture_samr_context); + ctx->num_objects_large_dc = 150; + + torture_rpc_tcase_add_test_ex(tcase, "many_aliases", + torture_rpc_samr_many_aliases, ctx); + torture_rpc_tcase_add_test_ex(tcase, "many_groups", + torture_rpc_samr_many_groups, ctx); + torture_rpc_tcase_add_test_ex(tcase, "many_accounts", + torture_rpc_samr_many_accounts, ctx); + + return suite; +} + +static bool torture_rpc_samr_badpwdcount(struct torture_context *torture, + struct dcerpc_pipe *p2, + struct cli_credentials *machine_credentials) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_PASSWORDS_BADPWDCOUNT; + ctx->machine_credentials = machine_credentials; + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +struct torture_suite *torture_rpc_samr_passwords_badpwdcount(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.passwords.badpwdcount"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "samr", + &ndr_table_samr, + TEST_ACCOUNT_NAME_PWD); + + torture_rpc_tcase_add_test_creds(tcase, "badPwdCount", + torture_rpc_samr_badpwdcount); + + return suite; +} + +static bool torture_rpc_samr_lockout(struct torture_context *torture, + struct dcerpc_pipe *p2, + struct cli_credentials *machine_credentials) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + bool ret = true; + struct torture_samr_context *ctx; + struct dcerpc_binding_handle *b; + + status = torture_rpc_connection(torture, &p, &ndr_table_samr); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + b = p->binding_handle; + + ctx = talloc_zero(torture, struct torture_samr_context); + + ctx->choice = TORTURE_SAMR_PASSWORDS_LOCKOUT; + ctx->machine_credentials = machine_credentials; + + ret &= test_Connect(b, torture, &ctx->handle); + + ret &= test_EnumDomains(p, torture, ctx); + + ret &= test_samr_handle_Close(b, torture, &ctx->handle); + + return ret; +} + +struct torture_suite *torture_rpc_samr_passwords_lockout(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.passwords.lockout"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_bdc_rpc_iface_tcase(suite, "samr", + &ndr_table_samr, + TEST_ACCOUNT_NAME_PWD); + + torture_rpc_tcase_add_test_creds(tcase, "lockout", + torture_rpc_samr_lockout); + + return suite; +} + +struct torture_suite *torture_rpc_samr_passwords_validate(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.passwords.validate"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "samr", + &ndr_table_samr); + torture_rpc_tcase_add_test(tcase, "validate", + test_samr_ValidatePassword); + + return suite; +} diff --git a/source4/torture/rpc/samr_accessmask.c b/source4/torture/rpc/samr_accessmask.c new file mode 100644 index 0000000..0d4c69b --- /dev/null +++ b/source4/torture/rpc/samr_accessmask.c @@ -0,0 +1,1197 @@ +/* + Unix SMB/CIFS implementation. + test suite for accessmasks on the SAMR pipe + + Copyright (C) Ronnie Sahlberg 2007 + Copyright (C) Guenther Deschner 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_c.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "libcli/security/security.h" + + +/* test user created to test the ACLs associated to SAMR objects */ +#define TEST_USER_NAME "samr_testuser" +#define TEST_MACHINENAME "samrtestmach" + +static NTSTATUS torture_samr_Close(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *h) +{ + NTSTATUS status; + struct samr_Close cl; + + cl.in.handle = h; + cl.out.handle = h; + status = dcerpc_samr_Close_r(b, tctx, &cl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return cl.out.result; +} + +static NTSTATUS torture_samr_Connect5(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t mask, struct policy_handle *h) +{ + NTSTATUS status; + struct samr_Connect5 r5; + union samr_ConnectInfo info; + uint32_t level_out = 0; + + info.info1.client_version = 0; + info.info1.supported_features = 0; + r5.in.system_name = ""; + r5.in.level_in = 1; + r5.in.info_in = &info; + r5.out.info_out = &info; + r5.out.level_out = &level_out; + r5.out.connect_handle = h; + r5.in.access_mask = mask; + + status = dcerpc_samr_Connect5_r(b, tctx, &r5); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return r5.out.result; +} + +/* check which bits in accessmask allows us to connect to the server */ +static bool test_samr_accessmask_Connect5(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct policy_handle h; + int i; + uint32_t mask; + struct dcerpc_binding_handle *b = p->binding_handle; + + printf("Testing which bits in accessmask allows us to connect\n"); + mask = 1; + for (i=0;i<33;i++) { + printf("Testing Connect5 with access mask 0x%08x", mask); + status = torture_samr_Connect5(tctx, b, mask, &h); + mask <<= 1; + + switch (i) { + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 23: + case 26: + case 27: + printf(" expecting to fail"); + /* of only one of these bits are set we expect to + fail by default + */ + if(!NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + break; + default: + /* these bits set are expected to succeed by default */ + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + status = torture_samr_Close(tctx, b, &h); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + } + printf(" OK\n"); + } + + return true; +} + +/* check which bits in accessmask allows us to EnumDomains() + by default we must specify at least one of : + SAMR/EnumDomains + Maximum + GenericAll + GenericRead + in the access mask to Connect5() in order to be allowed to perform + EnumDomains() on the policy handle returned from Connect5() +*/ +static bool test_samr_accessmask_EnumDomains(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct samr_EnumDomains ed; + struct policy_handle ch; + int i; + uint32_t mask; + uint32_t resume_handle = 0; + struct samr_SamArray *sam = NULL; + uint32_t num_entries = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + printf("Testing which bits in Connect5 accessmask allows us to EnumDomains\n"); + mask = 1; + for (i=0;i<33;i++) { + printf("Testing Connect5/EnumDomains with access mask 0x%08x", mask); + status = torture_samr_Connect5(tctx, b, mask, &ch); + mask <<= 1; + + switch (i) { + case 4: /* SAMR/EnumDomains */ + case 25: /* Maximum */ + case 28: /* GenericAll */ + case 31: /* GenericRead */ + /* these bits set are expected to succeed by default */ + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + ed.in.connect_handle = &ch; + ed.in.resume_handle = &resume_handle; + ed.in.buf_size = (uint32_t)-1; + ed.out.resume_handle = &resume_handle; + ed.out.num_entries = &num_entries; + ed.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomains_r(b, tctx, &ed), + "EnumDomains failed"); + if (!NT_STATUS_IS_OK(ed.out.result)) { + printf("EnumDomains failed - %s\n", nt_errstr(ed.out.result)); + return false; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + default: + printf(" expecting to fail"); + + if (!NT_STATUS_IS_OK(status)) { + printf(" OK\n"); + continue; + } + + ed.in.connect_handle = &ch; + ed.in.resume_handle = &resume_handle; + ed.in.buf_size = (uint32_t)-1; + ed.out.resume_handle = &resume_handle; + ed.out.num_entries = &num_entries; + ed.out.sam = &sam; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomains_r(b, tctx, &ed), + "EnumDomains failed"); + if(!NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, ed.out.result)) { + printf("EnumDomains failed - %s\n", nt_errstr(ed.out.result)); + return false; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + } + printf(" OK\n"); + } + + return true; +} + + +/* + * test how ACLs affect how/if a user can connect to the SAMR service + * + * samr_SetSecurity() returns SUCCESS when changing the ACL for + * a policy handle got from Connect5() but the ACL is not changed on + * the server + */ +static bool test_samr_connect_user_acl(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct cli_credentials *test_credentials, + const struct dom_sid *test_sid) + +{ + NTSTATUS status; + struct policy_handle ch; + struct policy_handle uch; + struct samr_QuerySecurity qs; + struct samr_SetSecurity ss; + struct security_ace ace; + struct security_descriptor *sd; + struct sec_desc_buf sdb, *sdbuf = NULL; + bool ret = true; + int sd_size; + struct dcerpc_pipe *test_p; + struct dcerpc_binding_handle *test_b; + const char *binding = torture_setting_string(tctx, "binding", NULL); + + printf("Testing ACLs to allow/prevent users to connect to SAMR"); + + /* connect to SAMR */ + status = torture_samr_Connect5(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + + /* get the current ACL for the SAMR policy handle */ + qs.in.handle = &ch; + qs.in.sec_info = SECINFO_DACL; + qs.out.sdbuf = &sdbuf; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QuerySecurity_r(b, tctx, &qs), + "QuerySecurity failed"); + if (!NT_STATUS_IS_OK(qs.out.result)) { + printf("QuerySecurity failed - %s\n", nt_errstr(qs.out.result)); + ret = false; + } + + /* how big is the security descriptor? */ + sd_size = sdbuf->sd_size; + + + /* add an ACE to the security descriptor to deny the user the + * 'connect to server' right + */ + sd = sdbuf->sd; + ace.type = SEC_ACE_TYPE_ACCESS_DENIED; + ace.flags = 0; + ace.access_mask = SAMR_ACCESS_CONNECT_TO_SERVER; + ace.trustee = *test_sid; + status = security_descriptor_dacl_add(sd, &ace); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to add ACE to security descriptor\n"); + ret = false; + } + ss.in.handle = &ch; + ss.in.sec_info = SECINFO_DACL; + ss.in.sdbuf = &sdb; + sdb.sd = sd; + torture_assert_ntstatus_ok(tctx, dcerpc_samr_SetSecurity_r(b, tctx, &ss), + "SetSecurity failed"); + if (!NT_STATUS_IS_OK(ss.out.result)) { + printf("SetSecurity failed - %s\n", nt_errstr(ss.out.result)); + ret = false; + } + + + /* Try to connect as the test user */ + status = dcerpc_pipe_connect(tctx, + &test_p, binding, &ndr_table_samr, + test_credentials, tctx->ev, tctx->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + printf("dcerpc_pipe_connect failed: %s\n", nt_errstr(status)); + return false; + } + test_b = test_p->binding_handle; + + /* connect to SAMR as the user */ + status = torture_samr_Connect5(tctx, test_b, SEC_FLAG_MAXIMUM_ALLOWED, &uch); + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + /* disconnec the user */ + talloc_free(test_p); + + + /* read the sequrity descriptor back. it should not have changed + * eventhough samr_SetSecurity returned SUCCESS + */ + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QuerySecurity_r(b, tctx, &qs), + "QuerySecurity failed"); + if (!NT_STATUS_IS_OK(qs.out.result)) { + printf("QuerySecurity failed - %s\n", nt_errstr(qs.out.result)); + ret = false; + } + if (sd_size != sdbuf->sd_size) { + printf("security descriptor changed\n"); + ret = false; + } + + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + ret = false; + } + + if (ret == true) { + printf(" OK\n"); + } + return ret; +} + +/* + * test if the ACLs are enforced for users. + * a normal testuser only gets the rights provided in hte ACL for + * Everyone which does not include the SAMR_ACCESS_SHUTDOWN_SERVER + * right. If the ACLs are checked when a user connects + * a testuser that requests the accessmask with only this bit set + * the connect should fail. + */ +static bool test_samr_connect_user_acl_enforced(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct cli_credentials *test_credentials, + const struct dom_sid *test_sid) + +{ + NTSTATUS status; + struct policy_handle uch; + bool ret = true; + struct dcerpc_pipe *test_p; + struct dcerpc_binding_handle *test_b; + const char *binding = torture_setting_string(tctx, "binding", NULL); + + printf("Testing if ACLs are enforced for non domain admin users when connecting to SAMR"); + + + status = dcerpc_pipe_connect(tctx, + &test_p, binding, &ndr_table_samr, + test_credentials, tctx->ev, tctx->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + printf("dcerpc_pipe_connect failed: %s\n", nt_errstr(status)); + return false; + } + test_b = test_p->binding_handle; + + /* connect to SAMR as the user */ + status = torture_samr_Connect5(tctx, test_b, SAMR_ACCESS_SHUTDOWN_SERVER, &uch); + if (NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + printf(" OK\n"); + + /* disconnec the user */ + talloc_free(test_p); + + return ret; +} + +/* check which bits in accessmask allows us to LookupDomain() + by default we must specify at least one of : + in the access mask to Connect5() in order to be allowed to perform + case 5: samr/opendomain + case 25: Maximum + case 28: GenericAll + case 29: GenericExecute + LookupDomain() on the policy handle returned from Connect5() +*/ +static bool test_samr_accessmask_LookupDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct samr_LookupDomain ld; + struct dom_sid2 *sid = NULL; + struct policy_handle ch; + struct lsa_String dn; + int i; + uint32_t mask; + struct dcerpc_binding_handle *b = p->binding_handle; + + printf("Testing which bits in Connect5 accessmask allows us to LookupDomain\n"); + mask = 1; + for (i=0;i<33;i++) { + printf("Testing Connect5/LookupDomain with access mask 0x%08x", mask); + status = torture_samr_Connect5(tctx, b, mask, &ch); + mask <<= 1; + + switch (i) { + case 5: + case 25: /* Maximum */ + case 28: /* GenericAll */ + case 29: /* GenericExecute */ + /* these bits set are expected to succeed by default */ + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + ld.in.connect_handle = &ch; + ld.in.domain_name = &dn; + ld.out.sid = &sid; + dn.string = lpcfg_workgroup(tctx->lp_ctx); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &ld), + "LookupDomain failed"); + if (!NT_STATUS_IS_OK(ld.out.result)) { + printf("LookupDomain failed - %s\n", nt_errstr(ld.out.result)); + return false; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + default: + printf(" expecting to fail"); + + if (!NT_STATUS_IS_OK(status)) { + printf(" OK\n"); + continue; + } + + ld.in.connect_handle = &ch; + ld.in.domain_name = &dn; + ld.out.sid = &sid; + dn.string = lpcfg_workgroup(tctx->lp_ctx); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &ld), + "LookupDomain failed"); + if(!NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, ld.out.result)) { + printf("LookupDomain failed - %s\n", nt_errstr(ld.out.result)); + return false; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + } + printf(" OK\n"); + } + + return true; +} + +/* check which bits in accessmask allows us to OpenDomain() + by default we must specify at least one of : + samr/opendomain + Maximum + GenericAll + GenericExecute + in the access mask to Connect5() in order to be allowed to perform + OpenDomain() on the policy handle returned from Connect5() +*/ +static bool test_samr_accessmask_OpenDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct samr_LookupDomain ld; + struct dom_sid2 *sid = NULL; + struct samr_OpenDomain od; + struct policy_handle ch; + struct policy_handle dh; + struct lsa_String dn; + int i; + uint32_t mask; + struct dcerpc_binding_handle *b = p->binding_handle; + + + /* first we must grab the sid of the domain */ + status = torture_samr_Connect5(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + ld.in.connect_handle = &ch; + ld.in.domain_name = &dn; + ld.out.sid = &sid; + dn.string = lpcfg_workgroup(tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &ld), + "LookupDomain failed"); + if (!NT_STATUS_IS_OK(ld.out.result)) { + printf("LookupDomain failed - %s\n", nt_errstr(ld.out.result)); + return false; + } + + + + printf("Testing which bits in Connect5 accessmask allows us to OpenDomain\n"); + mask = 1; + for (i=0;i<33;i++) { + printf("Testing Connect5/OpenDomain with access mask 0x%08x", mask); + status = torture_samr_Connect5(tctx, b, mask, &ch); + mask <<= 1; + + switch (i) { + case 5: + case 25: /* Maximum */ + case 28: /* GenericAll */ + case 29: /* GenericExecute */ + /* these bits set are expected to succeed by default */ + if (!NT_STATUS_IS_OK(status)) { + printf("Connect5 failed - %s\n", nt_errstr(status)); + return false; + } + + od.in.connect_handle = &ch; + od.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + od.in.sid = sid; + od.out.domain_handle = &dh; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenDomain_r(b, tctx, &od), + "OpenDomain failed"); + if (!NT_STATUS_IS_OK(od.out.result)) { + printf("OpenDomain failed - %s\n", nt_errstr(od.out.result)); + return false; + } + + status = torture_samr_Close(tctx, b, &dh); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + default: + printf(" expecting to fail"); + + if (!NT_STATUS_IS_OK(status)) { + printf(" OK\n"); + continue; + } + + status = torture_samr_Close(tctx, b, &ch); + if (!NT_STATUS_IS_OK(status)) { + printf("Close failed - %s\n", nt_errstr(status)); + return false; + } + break; + } + printf(" OK\n"); + } + + return true; +} + +static bool test_samr_connect(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + void *testuser; + const char *testuser_passwd; + struct cli_credentials *test_credentials; + bool ret = true; + const struct dom_sid *test_sid; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "Skipping test against Samba 3"); + } + + /* create a test user */ + testuser = torture_create_testuser(tctx, TEST_USER_NAME, lpcfg_workgroup(tctx->lp_ctx), + ACB_NORMAL, &testuser_passwd); + if (!testuser) { + printf("Failed to create test user\n"); + return false; + } + test_credentials = cli_credentials_init(tctx); + cli_credentials_set_workstation(test_credentials, "localhost", CRED_SPECIFIED); + cli_credentials_set_domain(test_credentials, lpcfg_workgroup(tctx->lp_ctx), + CRED_SPECIFIED); + cli_credentials_set_username(test_credentials, TEST_USER_NAME, CRED_SPECIFIED); + cli_credentials_set_password(test_credentials, testuser_passwd, CRED_SPECIFIED); + test_sid = torture_join_user_sid(testuser); + + + /* test if ACLs can be changed for the policy handle + * returned by Connect5 + */ + if (!test_samr_connect_user_acl(tctx, b, test_credentials, test_sid)) { + ret = false; + } + + /* test if the ACLs that are reported from the Connect5 + * policy handle is enforced. + * i.e. an ordinary user only has the same rights as Everybody + * ReadControl + * Samr/OpenDomain + * Samr/EnumDomains + * Samr/ConnectToServer + * is granted and should therefore not be able to connect when + * requesting SAMR_ACCESS_SHUTDOWN_SERVER + */ + if (!test_samr_connect_user_acl_enforced(tctx, b, test_credentials, test_sid)) { + ret = false; + } + + /* remove the test user */ + torture_leave_domain(tctx, testuser); + + return ret; +} + +struct torture_suite *torture_rpc_samr_accessmask(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.accessmask"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "samr", + &ndr_table_samr); + + torture_rpc_tcase_add_test(tcase, "connect", test_samr_connect); + + /* test which bits in the accessmask to Connect5 will allow + * us to call OpenDomain() */ + torture_rpc_tcase_add_test(tcase, "OpenDomain", + test_samr_accessmask_OpenDomain); + + /* test which bits in the accessmask to Connect5 will allow + * us to call LookupDomain() */ + torture_rpc_tcase_add_test(tcase, "LookupDomain", + test_samr_accessmask_LookupDomain); + + /* test which bits in the accessmask to Connect5 will allow + * us to call EnumDomains() */ + torture_rpc_tcase_add_test(tcase, "EnumDomains", + test_samr_accessmask_EnumDomains); + + /* test which bits in the accessmask to Connect5 + will allow us to connect to the server */ + torture_rpc_tcase_add_test(tcase, "Connect5", + test_samr_accessmask_Connect5); + + return suite; +} + +static bool test_LookupRids(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + uint32_t rid) +{ + struct samr_LookupRids r; + struct lsa_Strings names; + struct samr_Ids types; + + torture_comment(tctx, "Testing LookupRids %d\n", rid); + + r.in.domain_handle = domain_handle; + r.in.num_rids = 1; + r.in.rids = &rid; + r.out.names = &names; + r.out.types = &types; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupRids_r(b, tctx, &r), + "failed to call samr_LookupRids"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to call samr_LookupRids"); + + return true; +} + + +static bool test_user(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + uint32_t access_mask, + struct samr_DispEntryGeneral *u) +{ + struct policy_handle user_handle; + + torture_comment(tctx, "Testing user %s (%d)\n", u->account_name.string, u->rid); + + torture_assert(tctx, test_LookupRids(tctx, b, domain_handle, u->rid), + "failed to call lookuprids"); + + { + struct samr_OpenUser r; + + r.in.domain_handle = domain_handle; + r.in.access_mask = access_mask; + r.in.rid = u->rid; + r.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenUser_r(b, tctx, &r), + "failed to open user"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to open user"); + } + { + struct samr_QueryUserInfo r; + union samr_UserInfo *info; + uint32_t levels[] = { 16, 21 }; + int i; + + r.in.user_handle = &user_handle; + r.out.info = &info; + + for (i=0; i < ARRAY_SIZE(levels); i++) { + + r.in.level = levels[i]; + + torture_comment(tctx, "Testing QueryUserInfo rid: %d level: %d\n", + u->rid, r.in.level); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryUserInfo_r(b, tctx, &r), + talloc_asprintf(tctx, "failed to query user info level %d", r.in.level)); + torture_assert_ntstatus_ok(tctx, r.out.result, + talloc_asprintf(tctx, "failed to query user info level %d", r.in.level)); + } + } + { + struct samr_GetGroupsForUser r; + struct samr_RidWithAttributeArray *rids; + + r.in.user_handle = &user_handle; + r.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetGroupsForUser_r(b, tctx, &r), + "failed to query groups for user"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query groups for user"); + } + + torture_assert_ntstatus_ok(tctx, + torture_samr_Close(tctx, b, &user_handle), + "failed to close user handle"); + + return true; +} + +static bool test_samr_group(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + uint32_t access_mask, + struct samr_SamEntry *g) +{ + struct policy_handle group_handle; + + torture_comment(tctx, "Testing group %s (%d)\n", g->name.string, g->idx); + + torture_assert(tctx, test_LookupRids(tctx, b, domain_handle, g->idx), + "failed to call lookuprids"); + { + struct samr_OpenGroup r; + + r.in.domain_handle = domain_handle; + r.in.access_mask = access_mask; + r.in.rid = g->idx; + r.out.group_handle = &group_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenGroup_r(b, tctx, &r), + "failed to open group"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to open group"); + } + { + struct samr_QueryGroupMember r; + struct samr_RidAttrArray *rids; + + r.in.group_handle = &group_handle; + r.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryGroupMember_r(b, tctx, &r), + "failed to query group member"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to query group member"); + + } + + torture_assert_ntstatus_ok(tctx, + torture_samr_Close(tctx, b, &group_handle), + "failed to close group handle"); + + return true; +} + +static bool test_samr_alias(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + struct samr_SamEntry *a) +{ + torture_comment(tctx, "Testing alias %s (%d)\n", a->name.string, a->idx); + + torture_assert(tctx, test_LookupRids(tctx, b, domain_handle, a->idx), + "failed to call lookuprids"); + + { + struct samr_GetAliasMembership r; + struct lsa_SidArray sids; + struct samr_Ids rids; + + ZERO_STRUCT(sids); + + r.in.domain_handle = domain_handle; + r.in.sids = &sids; + r.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetAliasMembership_r(b, tctx, &r), + "failed to get alias membership"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to get alias membership"); + } + + + return true; +} + +static bool test_samr_domain(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t access_mask, + const char *domain_name, + struct policy_handle *connect_handle, + struct policy_handle *domain_handle_p) +{ + struct policy_handle domain_handle; + struct dom_sid *domain_sid; + + if (!domain_name) { + struct samr_EnumDomains r; + uint32_t resume_handle; + struct samr_SamArray *sam; + uint32_t num_entries; + int i; + + r.in.connect_handle = connect_handle; + r.in.buf_size = 0xffff; + r.in.resume_handle = &resume_handle; + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomains_r(b, tctx, &r), + "failed to enum domains"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to enum domains"); + + torture_assert_int_equal(tctx, num_entries, 2, + "unexpected number of domains"); + + torture_assert(tctx, sam, + "no domain pointer returned"); + + for (i=0; i < sam->count; i++) { + if (!strequal(sam->entries[i].name.string, "builtin")) { + domain_name = sam->entries[i].name.string; + break; + } + } + + torture_assert(tctx, domain_name, + "no domain found other than builtin found"); + } + + { + struct samr_LookupDomain r; + struct dom_sid2 *sid; + struct lsa_String name; + + name.string = talloc_strdup(tctx, domain_name); + + r.in.connect_handle = connect_handle; + r.in.domain_name = &name; + r.out.sid = &sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_LookupDomain_r(b, tctx, &r), + "failed to lookup domain"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to lookup domain"); + + domain_sid = dom_sid_dup(tctx, sid); + } + + { + struct samr_OpenDomain r; + + r.in.connect_handle = connect_handle; + r.in.access_mask = access_mask; + r.in.sid = domain_sid; + r.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenDomain_r(b, tctx, &r), + "failed to open domain"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to open domain"); + + } + + { + struct samr_QueryDomainInfo r; + union samr_DomainInfo *info; + uint32_t levels[] = { 1, 2, 8, 12 }; + int i; + + r.in.domain_handle = &domain_handle; + r.out.info = &info; + + for (i=0; i < ARRAY_SIZE(levels); i++) { + + r.in.level = levels[i]; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDomainInfo_r(b, tctx, &r), + talloc_asprintf(tctx, "failed to query domain info level %d", r.in.level)); + torture_assert_ntstatus_ok(tctx, r.out.result, + talloc_asprintf(tctx, "failed to query domain info level %d", r.in.level)); + } + + } + + *domain_handle_p = domain_handle; + + return true; +} + +static void get_query_dispinfo_params(int loop_count, + uint32_t *max_entries, + uint32_t *buf_size) +{ + switch(loop_count) { + case 0: + *max_entries = 512; + *buf_size = 16383; + break; + case 1: + *max_entries = 1024; + *buf_size = 32766; + break; + case 2: + *max_entries = 2048; + *buf_size = 65532; + break; + case 3: + *max_entries = 4096; + *buf_size = 131064; + break; + default: /* loop_count >= 4 */ + *max_entries = 4096; + *buf_size = 131071; + break; + } +} + + +static bool test_samr_users(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t access_mask, + struct policy_handle *domain_handle) +{ + { + struct samr_QueryDisplayInfo r; + uint32_t total_size; + uint32_t returned_size; + union samr_DispInfo info; + int loop_count = 0; + + r.in.domain_handle = domain_handle; + r.in.level = 1; + r.in.start_idx = 0; + + r.out.total_size = &total_size; + r.out.returned_size = &returned_size; + r.out.info = &info; + + do { + int i; + + r.in.max_entries = 0xffff; + r.in.buf_size = 0xffff; + + get_query_dispinfo_params(loop_count, + &r.in.max_entries, + &r.in.buf_size); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_QueryDisplayInfo_r(b, tctx, &r), + "QueryDisplayInfo failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to call QueryDisplayInfo"); + } + + for (i=0; i < info.info1.count; i++) { + torture_assert(tctx, + test_user(tctx, b, domain_handle, access_mask, &info.info1.entries[i]), + "failed to test user"); + } + loop_count++; + r.in.start_idx += info.info1.count; + + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + +static bool test_samr_groups(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t access_mask, + struct policy_handle *domain_handle) +{ + { + struct samr_EnumDomainGroups r; + uint32_t resume_handle = 0; + struct samr_SamArray *sam; + uint32_t num_entries; + + r.in.domain_handle = domain_handle; + r.in.resume_handle = &resume_handle; + r.in.max_size = 0xFFFF; + + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + do { + int i; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainGroups_r(b, tctx, &r), + "EnumDomainGroups failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to call EnumDomainGroups"); + } + + for (i=0; i < num_entries; i++) { + torture_assert(tctx, + test_samr_group(tctx, b, domain_handle, access_mask, &sam->entries[i]), + "failed to test group"); + } + + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + +static bool test_samr_aliases(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t access_mask, + struct policy_handle *domain_handle) +{ + { + struct samr_EnumDomainAliases r; + uint32_t resume_handle = 0; + struct samr_SamArray *sam; + uint32_t num_entries; + + r.in.domain_handle = domain_handle; + r.in.resume_handle = &resume_handle; + r.in.max_size = 0xFFFF; + + r.out.sam = &sam; + r.out.num_entries = &num_entries; + r.out.resume_handle = &resume_handle; + + do { + int i; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_EnumDomainAliases_r(b, tctx, &r), + "EnumDomainAliases failed"); + if (NT_STATUS_IS_ERR(r.out.result)) { + torture_assert_ntstatus_ok(tctx, r.out.result, + "failed to call EnumDomainAliases"); + } + + for (i=0; i < num_entries; i++) { + torture_assert(tctx, + test_samr_alias(tctx, b, domain_handle, &sam->entries[i]), + "failed to test alias"); + } + + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return true; +} + +static bool torture_rpc_samr_workstation_query(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct cli_credentials *machine_credentials) +{ + struct policy_handle connect_handle; + struct policy_handle domain_handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert_ntstatus_ok(tctx, + torture_samr_Connect5(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, + &connect_handle), + "failed to connect to samr server"); + + torture_assert(tctx, + test_samr_domain(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, + lpcfg_workgroup(tctx->lp_ctx), + &connect_handle, &domain_handle), + "failed to test domain"); + + torture_assert(tctx, + test_samr_users(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, + &domain_handle), + "failed to test users"); + + torture_assert(tctx, + test_samr_groups(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, + &domain_handle), + "failed to test groups"); + + torture_assert(tctx, + test_samr_aliases(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, + &domain_handle), + "failed to test aliases"); + + torture_assert_ntstatus_ok(tctx, + torture_samr_Close(tctx, b, &domain_handle), + "failed to close domain handle"); + + torture_assert_ntstatus_ok(tctx, + torture_samr_Close(tctx, b, &connect_handle), + "failed to close connect handle"); + + return true; +} + +/* The purpose of this test is to verify that an account authenticated as a + * domain member workstation can query a DC for various remote read calls all + * opening objects while requesting SEC_FLAG_MAXIMUM_ALLOWED access rights on + * the object open calls. This is the behavior of winbind (and most of samba's + * client code) - gd */ + +struct torture_suite *torture_rpc_samr_workstation_auth(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "samr.machine.auth"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "samr", + &ndr_table_samr, + TEST_MACHINENAME); + + torture_rpc_tcase_add_test_creds(tcase, "workstation_query", + torture_rpc_samr_workstation_query); + + return suite; +} diff --git a/source4/torture/rpc/samr_handletype.c b/source4/torture/rpc/samr_handletype.c new file mode 100644 index 0000000..ab5e7d0 --- /dev/null +++ b/source4/torture/rpc/samr_handletype.c @@ -0,0 +1,217 @@ +/* + Unix SMB/CIFS implementation. + + test suite for handle types on the SAMR pipe + + Copyright (C) Samuel Cabrero <scabrero@samba.org> 2020 + + 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_c.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "libcli/security/security.h" + +enum samr_handle { + SAMR_HANDLE_CONNECT, + SAMR_HANDLE_DOMAIN, + SAMR_HANDLE_USER, + SAMR_HANDLE_GROUP, + SAMR_HANDLE_ALIAS +}; + +static NTSTATUS torture_samr_Close(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *h) +{ + NTSTATUS status; + struct samr_Close cl; + + cl.in.handle = h; + cl.out.handle = h; + status = dcerpc_samr_Close_r(b, tctx, &cl); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return cl.out.result; +} + +static NTSTATUS torture_samr_Connect5(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t mask, struct policy_handle *h) +{ + NTSTATUS status; + struct samr_Connect5 r5; + union samr_ConnectInfo info; + uint32_t level_out = 0; + + info.info1.client_version = 0; + info.info1.supported_features = 0; + r5.in.system_name = ""; + r5.in.level_in = 1; + r5.in.info_in = &info; + r5.out.info_out = &info; + r5.out.level_out = &level_out; + r5.out.connect_handle = h; + r5.in.access_mask = mask; + + status = dcerpc_samr_Connect5_r(b, tctx, &r5); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return r5.out.result; +} + +static bool test_samr_handletype_OpenDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct samr_LookupDomain ld; + struct dom_sid2 *sid = NULL; + struct samr_OpenDomain od; + struct samr_OpenUser ou; + struct samr_OpenGroup og; + struct policy_handle ch; + struct policy_handle bad; + struct policy_handle dh; + struct policy_handle oh; + struct lsa_String dn; + struct dcerpc_binding_handle *b = p->binding_handle; + + /* first we must grab the sid of the domain */ + status = torture_samr_Connect5(tctx, b, SEC_FLAG_MAXIMUM_ALLOWED, &ch); + torture_assert_ntstatus_ok(tctx, status, "Connect5 failed"); + + ld.in.connect_handle = &ch; + ld.in.domain_name = &dn; + ld.out.sid = &sid; + dn.string = lpcfg_workgroup(tctx->lp_ctx); + status = dcerpc_samr_LookupDomain_r(b, tctx, &ld); + torture_assert_ntstatus_ok(tctx, status, "LookupDomain failed"); + torture_assert_ntstatus_ok(tctx, ld.out.result, "LookupDomain failed"); + + status = torture_samr_Connect5(tctx, b, 1, &ch); + torture_assert_ntstatus_ok(tctx, status, "Connect5 failed"); + + od.in.connect_handle = &bad; + od.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + od.in.sid = sid; + od.out.domain_handle = &dh; + + /* Open domain, wrong handle GUID */ + bad = ch; + bad.uuid = GUID_random(); + + status = dcerpc_samr_OpenDomain_r(b, tctx, &od); + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "OpenDomain succeeded with random GUID"); + + /* Open domain, wrong handle type */ + bad = ch; + bad.handle_type = SAMR_HANDLE_USER; + + status = dcerpc_samr_OpenDomain_r(b, tctx, &od); + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "OpenDomain succeeded with wrong type"); + + /* Open domain */ + bad = ch; + + status = dcerpc_samr_OpenDomain_r(b, tctx, &od); + torture_assert_ntstatus_ok(tctx, status, "OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, od.out.result, "OpenDomain failed"); + + bad = dh; + + /* Open user, wrong handle type */ + ou.in.domain_handle = &bad; + ou.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + ou.in.rid = 501; + ou.out.user_handle = &oh; + + bad.handle_type = SAMR_HANDLE_ALIAS; + + status = dcerpc_samr_OpenUser_r(b, tctx, &ou); + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "OpenUser succeeded with wrong type"); + + /* Open user */ + bad.handle_type = SAMR_HANDLE_DOMAIN; + + status = dcerpc_samr_OpenUser_r(b, tctx, &ou); + torture_assert_ntstatus_ok(tctx, status, "OpenUser failed"); + torture_assert_ntstatus_ok(tctx, ou.out.result, "OpenUser failed"); + + /* Close user */ + status = torture_samr_Close(tctx, b, &oh); + torture_assert_ntstatus_ok(tctx, status, "Close failed"); + + bad = dh; + + /* Open group, wrong type */ + og.in.domain_handle = &bad; + og.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + og.in.rid = 513; + og.out.group_handle = &oh; + + bad.handle_type = SAMR_HANDLE_GROUP; + + status = dcerpc_samr_OpenGroup_r(b, tctx, &og); + torture_assert_ntstatus_equal(tctx, + status, + NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "OpenGroup succeeded with wrong type"); + + /* Open group */ + bad.handle_type = SAMR_HANDLE_DOMAIN; + + status = dcerpc_samr_OpenGroup_r(b, tctx, &og); + torture_assert_ntstatus_ok(tctx, status, "OpenGroup failed"); + torture_assert_ntstatus_ok(tctx, ou.out.result, "OpenGroup failed"); + + /* Close group */ + status = torture_samr_Close(tctx, b, &oh); + torture_assert_ntstatus_ok(tctx, status, "Close failed"); + + /* Close connect */ + status = torture_samr_Close(tctx, b, &ch); + torture_assert_ntstatus_ok(tctx, status, "Close failed"); + + return true; +} + +struct torture_suite *torture_rpc_samr_handletype(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = NULL; + struct torture_rpc_tcase *tcase = NULL; + + suite = torture_suite_create(mem_ctx, "samr.handletype"); + tcase = torture_suite_add_rpc_iface_tcase(suite, "samr", + &ndr_table_samr); + + torture_rpc_tcase_add_test(tcase, "OpenDomainHandleType", + test_samr_handletype_OpenDomain); + + return suite; +} diff --git a/source4/torture/rpc/samr_priv.c b/source4/torture/rpc/samr_priv.c new file mode 100644 index 0000000..a5aa4b7 --- /dev/null +++ b/source4/torture/rpc/samr_priv.c @@ -0,0 +1,580 @@ +/* + * Unix SMB/CIFS implementation. + * test suite for samr rpc operations + * + * Copyright (c) 2011 Andreas Schneider + * + * 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/param.h" +#include "torture/torture.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/security/security.h" +#include "torture/rpc/torture_rpc.h" + +#define TEST_ACCOUNT_NAME "guru" + +struct torture_user { + const char *username; + const char *password; + const char *domain; + uint32_t *builtin_memberships; + uint32_t num_builtin_memberships; + bool admin_rights; +}; + +struct torture_access_context { + struct dcerpc_pipe *pipe; + struct torture_user user; + struct test_join *join; +}; + +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; +} + +static bool test_samr_queryUserInfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *user_handle) +{ + struct samr_QueryUserInfo r; + union samr_UserInfo *info; + NTSTATUS status; + + r.in.level = UserGeneralInformation; + r.in.user_handle = user_handle; + r.out.info = &info; + + status = dcerpc_samr_QueryUserInfo_r(b, + tctx, + &r); + torture_assert_ntstatus_ok(tctx, status, "queryUserInfo failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "queryUserInfo failed"); + + return true; +} + +static bool test_LookupName(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *domain_handle, + const char *name, + uint32_t *rid) +{ + NTSTATUS status; + struct samr_LookupNames n; + struct lsa_String sname[1]; + struct samr_Ids rids, types; + + init_lsa_String(&sname[0], name); + + n.in.domain_handle = domain_handle; + n.in.num_names = 1; + n.in.names = sname; + n.out.rids = &rids; + n.out.types = &types; + + status = dcerpc_samr_LookupNames_r(b, tctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + if (!NT_STATUS_IS_OK(n.out.result)) { + return false; + } + + *rid = n.out.rids->ids[0]; + return true; +} + +static bool test_samr_CreateUser(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + const char *name, + struct policy_handle *user_handle) +{ + struct lsa_String username; + struct samr_CreateUser r; + uint32_t rid = 0; + NTSTATUS status; + + init_lsa_String(&username, name); + + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.domain_handle = domain_handle; + r.in.account_name = &username; + r.out.user_handle = user_handle; + r.out.rid = &rid; + + status = dcerpc_samr_CreateUser_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "CreateUser failed"); + + return NT_STATUS_IS_OK(r.out.result); +} + +static bool test_samr_OpenUser(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *domain_handle, + const char *name, + struct policy_handle *user_handle, + bool expected) +{ + struct samr_OpenUser r; + uint32_t rid = 0; + NTSTATUS status; + bool ok; + + ok = test_LookupName(b, tctx, domain_handle, name, &rid); + if (!ok && expected) { + torture_comment(tctx, " - lookup name for %s failed\n", name); + return true; + } else if (!ok) { + return false; + } + + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.domain_handle = domain_handle; + r.in.rid = rid; + r.out.user_handle = user_handle; + + status = dcerpc_samr_OpenUser_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "OpenUser failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "OpenUser failed"); + + return true; +} + +static bool test_samr_openDomain(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *connect_handle, + const char *domain, + struct policy_handle *domain_handle) +{ + struct samr_LookupDomain r; + struct samr_OpenDomain r2; + struct lsa_String n; + struct dom_sid *sid; + NTSTATUS status; + + r.in.connect_handle = connect_handle; + init_lsa_String(&n, domain); + r.in.domain_name = &n; + r.out.sid = &sid; + + status = dcerpc_samr_LookupDomain_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "LookupDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "LookupDomain failed"); + + r2.in.connect_handle = connect_handle; + r2.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r2.in.sid = sid; + r2.out.domain_handle = domain_handle; + + status = dcerpc_samr_OpenDomain_r(b, tctx, &r2); + torture_assert_ntstatus_ok(tctx, status, "OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, r2.out.result, "OpenDomain failed"); + + return true; +} + +static bool test_samr_Connect(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *connect_handle) +{ + struct samr_Connect r; + NTSTATUS status; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.connect_handle = connect_handle; + + status = dcerpc_samr_Connect_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "SAMR connect failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "SAMR connect failed"); + + return true; +} + +static bool test_samr_create_user(struct torture_context *tctx, + struct torture_access_context *t, + const char *name) +{ + struct dcerpc_binding_handle *b = t->pipe->binding_handle; + struct policy_handle connect_handle; + struct policy_handle domain_handle; + struct policy_handle user_handle; + bool ok = false; + + torture_comment(tctx, "Connecting to SAMR\n"); + ZERO_STRUCT(connect_handle); + ok = test_samr_Connect(tctx, b, &connect_handle); + torture_assert(tctx, ok, "Unable to connect to domain"); + + torture_comment(tctx, "Opening domain %s\n", t->user.domain); + ZERO_STRUCT(domain_handle); + ok = test_samr_openDomain(tctx, + b, + &connect_handle, + t->user.domain, + &domain_handle); + torture_assert(tctx, ok, "Unable to open to domain"); + + torture_comment(tctx, "Creating account %s\n", name); + ZERO_STRUCT(user_handle); + ok = test_samr_CreateUser(tctx, + b, + &domain_handle, + name, + &user_handle); + + /* We don't check ok with torture macros here because the + * caller might be looking for failure */ + test_samr_handle_Close(b, tctx, &domain_handle); + test_samr_handle_Close(b, tctx, &connect_handle); + + return ok; +} + +static bool test_samr_userinfo_getinfo(struct torture_context *tctx, + struct dcerpc_pipe *p, + bool expected) +{ + const char *name; + struct dcerpc_pipe *p2 = NULL; + struct dcerpc_binding_handle *b; + struct policy_handle connect_handle; + struct policy_handle domain_handle; + struct policy_handle user_handle; + NTSTATUS status; + uint32_t i = 0; + bool ok; + + status = torture_rpc_connection(tctx, &p2, &ndr_table_samr); + torture_assert_ntstatus_ok(tctx, status, + "Creating secondary connection failed"); + b = p2->binding_handle; + + torture_comment(tctx, " - 2nd connect\n"); + /* connect */ + ZERO_STRUCT(connect_handle); + ok = test_samr_Connect(tctx, b, &connect_handle); + torture_assert(tctx, ok, "Unable to connect to domain"); + + torture_comment(tctx, " - 2nd open domain\n"); + /* open domain */ + ZERO_STRUCT(domain_handle); + ok = test_samr_openDomain(tctx, + b, + &connect_handle, + torture_setting_string(tctx, "workgroup", + lpcfg_workgroup(tctx->lp_ctx)), + &domain_handle); + torture_assert(tctx, ok, "Unable to open to domain"); + + /* create user */ + name = talloc_asprintf(tctx, + "%s%04d", + TEST_ACCOUNT_NAME, + i); + + torture_comment(tctx, " - 2nd open user\n"); + ZERO_STRUCT(user_handle); + ok = test_samr_OpenUser(tctx, + b, + &domain_handle, + name, + &user_handle, + expected); + torture_assert(tctx, ok, "Unable to open user"); + + if (!expected) { + torture_comment(tctx, " - 2nd query user\n"); + ok = test_samr_queryUserInfo(tctx, b, &user_handle); + torture_assert(tctx, ok, "Unable to query user"); + + test_samr_handle_Close(b, tctx, &user_handle); + } + + test_samr_handle_Close(b, tctx, &domain_handle); + test_samr_handle_Close(b, tctx, &connect_handle); + + talloc_free(p2); + + return true; +} + +#define NUM_RUNS 20 +static bool torture_rpc_samr_caching(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct test_join *join; + const char *password = NULL; + const char *name; + NTSTATUS status; + uint32_t i = 0; + bool ok; + + torture_comment(tctx, ">>> Testing User Info Caching\n"); + + /* create user */ + name = talloc_asprintf(tctx, + "%s%04d", + TEST_ACCOUNT_NAME, + i); + + torture_comment(tctx, "- Creating user %s\n", name); + + join = torture_create_testuser(tctx, + name, + torture_setting_string(tctx, "workgroup", + lpcfg_workgroup(tctx->lp_ctx)), + ACB_NORMAL, + &password); + torture_assert(tctx, join, "failed to join domain"); + + torture_comment(tctx, "- Query user information\n"); + for (i = 0; i < NUM_RUNS; i++) { + ok = test_samr_userinfo_getinfo(tctx, p, false); + torture_assert(tctx, ok, "test_samr_userinfo_getinfo failed"); + } + + torture_comment(tctx, "- Delete user\n"); + status = torture_delete_testuser(tctx, + join, + name); + torture_assert_ntstatus_ok(tctx, status, "DeleteUser failed"); + + torture_comment(tctx, "- Try to query user information again (should fail)\n"); + for (i = 0; i < NUM_RUNS; i++) { + ok = test_samr_userinfo_getinfo(tctx, + p, + true); + torture_assert(tctx, ok, "test_samr_userinfo_getinfo failed"); + } + + return true; +} +#undef NUM_RUNS + +static bool torture_rpc_samr_access_setup_membership(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint32_t num_members, + uint32_t *members, + struct dom_sid *user_sid) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle connect_handle, domain_handle; + int i; + + torture_comment(tctx, + "Setting up BUILTIN membership for %s\n", + dom_sid_string(tctx, user_sid)); + + for (i=0; i < num_members; i++) { + torture_comment(tctx, "adding user to S-1-5-32-%d\n", members[i]); + } + + /* connect */ + { + struct samr_Connect2 r; + r.in.system_name = ""; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + ZERO_STRUCT(connect_handle); + r.out.connect_handle = &connect_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_Connect2_r(b, tctx, &r), + "samr_Connect2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_Connect2 failed"); + } + + /* open domain */ + { + struct samr_OpenDomain r; + r.in.connect_handle = &connect_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.sid = dom_sid_parse_talloc(tctx, "S-1-5-32"); + ZERO_STRUCT(domain_handle); + r.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenDomain_r(b, tctx, &r), + "samr_OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_OpenDomain failed"); + } + + for (i = 0; i < num_members; i++) { + + struct policy_handle alias_handle; + + /* open alias */ + { + struct samr_OpenAlias r; + r.in.domain_handle = &domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = members[i]; + ZERO_STRUCT(alias_handle); + r.out.alias_handle = &alias_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenAlias_r(b, tctx, &r), + "samr_OpenAlias failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_OpenAlias failed"); + } + + /* add alias member */ + { + struct samr_AddAliasMember r; + ZERO_STRUCT(alias_handle); + r.in.alias_handle = &alias_handle; + r.in.sid = user_sid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_AddAliasMember_r(b, tctx, &r), + "samr_AddAliasMember failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_AddAliasMember failed"); + } + + test_samr_handle_Close(b, tctx, &alias_handle); + } + + test_samr_handle_Close(b, tctx, &domain_handle); + test_samr_handle_Close(b, tctx, &connect_handle); + + return true; +} + +static bool torture_rpc_samr_access_setup(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct torture_access_context *t) +{ + const char *binding = torture_setting_string(tctx, "binding", NULL); + struct cli_credentials *test_credentials; + struct test_join *join; + struct dom_sid *test_sid; + struct dcerpc_pipe *samr_pipe; + + t->user.domain = torture_setting_string(tctx, "workgroup", + lpcfg_workgroup(tctx->lp_ctx)), + + join = torture_create_testuser(tctx, + t->user.username, + t->user.domain, + ACB_NORMAL, + &t->user.password); + torture_assert(tctx, join, "failed to join domain"); + t->join = join; + + test_credentials = cli_credentials_init(tctx); + + cli_credentials_set_workstation(test_credentials, + "localhost", + CRED_SPECIFIED); + cli_credentials_set_domain(test_credentials, + torture_setting_string(tctx, "workgroup", + lpcfg_workgroup(tctx->lp_ctx)), + CRED_SPECIFIED); + cli_credentials_set_username(test_credentials, + t->user.username, + CRED_SPECIFIED); + cli_credentials_set_password(test_credentials, + t->user.password, + CRED_SPECIFIED); + test_sid = discard_const_p(struct dom_sid, + torture_join_user_sid(t->join)); + + if (t->user.num_builtin_memberships) { + torture_assert(tctx, + torture_rpc_samr_access_setup_membership(tctx, + p, + t->user.num_builtin_memberships, + t->user.builtin_memberships, + test_sid), + "failed to setup membership"); + } + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect(tctx, + &samr_pipe, + binding, + &ndr_table_samr, + test_credentials, + tctx->ev, + tctx->lp_ctx), + "Error connecting to server"); + + t->pipe = samr_pipe; + + return true; +} + +static bool torture_rpc_samr_access(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_access_context *t; + const char *testuser; + bool ok; + + torture_comment(tctx, "Testing non-privileged user access\n"); + + t = talloc_zero(tctx, struct torture_access_context); + torture_assert(tctx, t, "talloc failed"); + + t->user.username = talloc_asprintf(t, "%s%04d", TEST_ACCOUNT_NAME, 100); + + torture_comment(tctx, "*** Setting up non-privleged user\n" + "***\n"); + + ok = torture_rpc_samr_access_setup(tctx, p, t); + torture_assert(tctx, ok, "torture_rpc_samr_access_setup failed"); + + testuser = talloc_asprintf(t, "%s%04d", TEST_ACCOUNT_NAME, 200); + + torture_comment(tctx, "*** Try to create user (%s) as non-privileged " + "user - should fail\n" + "***\n", testuser); + + ok = test_samr_create_user(tctx, t, testuser); + + torture_assert(tctx, ok == false, "*** Creating user was successful but it should fail"); + + return true; +} + +struct torture_suite *torture_rpc_samr_priv(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = + torture_suite_create(mem_ctx, "samr.priv"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, + "samr", + &ndr_table_samr); + + torture_rpc_tcase_add_test(tcase, + "caching", + torture_rpc_samr_caching); + + torture_rpc_tcase_add_test(tcase, + "access", + torture_rpc_samr_access); + + return suite; +} diff --git a/source4/torture/rpc/samsync.c b/source4/torture/rpc/samsync.c new file mode 100644 index 0000000..a8541d3 --- /dev/null +++ b/source4/torture/rpc/samsync.c @@ -0,0 +1,1798 @@ +/* + Unix SMB/CIFS implementation. + + test suite for netlogon rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004 + Copyright (C) Tim Potter 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "../lib/crypto/crypto.h" +#include "system/time.h" +#include "torture/rpc/torture_rpc.h" +#include "auth/gensec/gensec.h" +#include "libcli/auth/libcli_auth.h" +#include "libcli/samsync/samsync.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "param/param.h" +#include "lib/crypto/gnutls_helpers.h" + +#define TEST_MACHINE_NAME "samsynctest" +#define TEST_WKSTA_MACHINE_NAME "samsynctest2" +#define TEST_USER_NAME "samsynctestuser" + +/* + try a netlogon SamLogon +*/ +static NTSTATUS test_SamLogon(struct torture_context *tctx, + struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds, + const char *domain, const char *account_name, + const char *workstation, + struct samr_Password *lm_hash, + struct samr_Password *nt_hash, + struct netr_SamInfo3 **info3) +{ + NTSTATUS status; + struct netr_LogonSamLogon r; + struct netr_Authenticator auth, auth2; + struct netr_NetworkInfo ninfo; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative; + struct dcerpc_binding_handle *b = p->binding_handle; + int rc; + + ninfo.identity_info.domain_name.string = domain; + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.account_name.string = account_name; + ninfo.identity_info.workstation.string = workstation; + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + if (nt_hash) { + ninfo.nt.length = 24; + ninfo.nt.data = talloc_array(mem_ctx, uint8_t, 24); + rc = SMBOWFencrypt(nt_hash->hash, ninfo.challenge, + ninfo.nt.data); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + } else { + ninfo.nt.length = 0; + ninfo.nt.data = NULL; + } + + if (lm_hash) { + ninfo.lm.length = 24; + ninfo.lm.data = talloc_array(mem_ctx, uint8_t, 24); + rc = SMBOWFencrypt(lm_hash->hash, ninfo.challenge, + ninfo.lm.data); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + } else { + ninfo.lm.length = 0; + ninfo.lm.data = NULL; + } + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf(mem_ctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = workstation; + r.in.credential = &auth; + r.in.return_authenticator = &auth2; + r.in.logon_level = NetlogonNetworkInformation; + r.in.logon = &logon; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + + ZERO_STRUCT(auth2); + netlogon_creds_client_authenticator(creds, &auth); + + r.in.validation_level = 3; + + status = dcerpc_netr_LogonSamLogon_r(b, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + if (info3) { + *info3 = validation.sam3; + } + + return r.out.result; +} + +struct samsync_state { +/* we remember the sequence numbers so we can easily do a DatabaseDelta */ + uint64_t seq_num[3]; + const char *domain_name[2]; + struct samsync_secret *secrets; + struct samsync_trusted_domain *trusted_domains; + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState *creds_netlogon_wksta; + struct policy_handle *connect_handle; + struct policy_handle *domain_handle[2]; + struct dom_sid *sid[2]; + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + struct dcerpc_pipe *p_netlogon_wksta; + struct dcerpc_pipe *p_samr; + struct dcerpc_binding_handle *b_samr; + struct dcerpc_pipe *p_lsa; + struct dcerpc_binding_handle *b_lsa; + struct policy_handle *lsa_handle; +}; + +struct samsync_secret { + struct samsync_secret *prev, *next; + DATA_BLOB secret; + const char *name; + NTTIME mtime; +}; + +struct samsync_trusted_domain { + struct samsync_trusted_domain *prev, *next; + struct dom_sid *sid; + const char *name; +}; + +static struct policy_handle *samsync_open_domain(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct samsync_state *samsync_state, + const char *domain, + struct dom_sid **sid_p) +{ + struct lsa_String name; + struct samr_OpenDomain o; + struct samr_LookupDomain l; + struct dom_sid2 *sid = NULL; + struct policy_handle *domain_handle = talloc(mem_ctx, struct policy_handle); + NTSTATUS nt_status; + + name.string = domain; + l.in.connect_handle = samsync_state->connect_handle; + l.in.domain_name = &name; + l.out.sid = &sid; + + nt_status = dcerpc_samr_LookupDomain_r(samsync_state->b_samr, mem_ctx, &l); + if (!NT_STATUS_IS_OK(nt_status)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(nt_status)); + return NULL; + } + if (!NT_STATUS_IS_OK(l.out.result)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(l.out.result)); + return NULL; + } + + o.in.connect_handle = samsync_state->connect_handle; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.in.sid = *l.out.sid; + o.out.domain_handle = domain_handle; + + if (sid_p) { + *sid_p = *l.out.sid; + } + + nt_status = dcerpc_samr_OpenDomain_r(samsync_state->b_samr, mem_ctx, &o); + if (!NT_STATUS_IS_OK(nt_status)) { + torture_comment(tctx, "OpenDomain failed - %s\n", nt_errstr(nt_status)); + return NULL; + } + if (!NT_STATUS_IS_OK(o.out.result)) { + torture_comment(tctx, "OpenDomain failed - %s\n", nt_errstr(o.out.result)); + return NULL; + } + + return domain_handle; +} + +static struct sec_desc_buf *samsync_query_samr_sec_desc(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct samsync_state *samsync_state, + struct policy_handle *handle) +{ + struct samr_QuerySecurity r; + struct sec_desc_buf *sdbuf = NULL; + NTSTATUS status; + + r.in.handle = handle; + r.in.sec_info = 0x7; + r.out.sdbuf = &sdbuf; + + status = dcerpc_samr_QuerySecurity_r(samsync_state->b_samr, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "SAMR QuerySecurity failed - %s\n", nt_errstr(status)); + return NULL; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "SAMR QuerySecurity failed - %s\n", nt_errstr(r.out.result)); + return NULL; + } + + return sdbuf; +} + +static struct sec_desc_buf *samsync_query_lsa_sec_desc(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, + struct samsync_state *samsync_state, + struct policy_handle *handle) +{ + struct lsa_QuerySecurity r; + struct sec_desc_buf *sdbuf = NULL; + NTSTATUS status; + + r.in.handle = handle; + r.in.sec_info = 0x7; + r.out.sdbuf = &sdbuf; + + status = dcerpc_lsa_QuerySecurity_r(samsync_state->b_lsa, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "LSA QuerySecurity failed - %s\n", nt_errstr(status)); + return NULL; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "LSA QuerySecurity failed - %s\n", nt_errstr(r.out.result)); + return NULL; + } + + return sdbuf; +} + +#define TEST_UINT64_EQUAL(i1, i2) do {\ + if (i1 != i2) {\ + torture_comment(tctx, "%s: uint64 mismatch: " #i1 ": 0x%016llx (%lld) != " #i2 ": 0x%016llx (%lld)\n", \ + __location__, \ + (long long)i1, (long long)i1, \ + (long long)i2, (long long)i2);\ + ret = false;\ + } \ +} while (0) +#define TEST_INT_EQUAL(i1, i2) do {\ + if (i1 != i2) {\ + torture_comment(tctx, "%s: integer mismatch: " #i1 ": 0x%08x (%d) != " #i2 ": 0x%08x (%d)\n", \ + __location__, i1, i1, i2, i2); \ + ret = false;\ + } \ +} while (0) +#define TEST_TIME_EQUAL(t1, t2) do {\ + if (t1 != t2) {\ + torture_comment(tctx, "%s: NTTIME mismatch: " #t1 ":%s != " #t2 ": %s\n", \ + __location__, nt_time_string(mem_ctx, t1), nt_time_string(mem_ctx, t2));\ + ret = false;\ + } \ +} while (0) + +#define TEST_STRING_EQUAL(s1, s2) do {\ + if (!((!s1.string || s1.string[0]=='\0') && (!s2.string || s2.string[0]=='\0')) \ + && strcmp_safe(s1.string, s2.string) != 0) {\ + torture_comment(tctx, "%s: string mismatch: " #s1 ":%s != " #s2 ": %s\n", \ + __location__, s1.string, s2.string);\ + ret = false;\ + } \ +} while (0) + +#define TEST_BINARY_STRING_EQUAL(s1, s2) do {\ + if (!((!s1.array || s1.array[0]=='\0') && (!s2.array || s2.array[0]=='\0')) \ + && memcmp(s1.array, s2.array, s1.length * 2) != 0) {\ + torture_comment(tctx, "%s: string mismatch: " #s1 ":%s != " #s2 ": %s\n", \ + __location__, (const char *)s1.array, (const char *)s2.array);\ + ret = false;\ + } \ +} while (0) + +#define TEST_SID_EQUAL(s1, s2) do {\ + if (!dom_sid_equal(s1, s2)) {\ + torture_comment(tctx, "%s: dom_sid mismatch: " #s1 ":%s != " #s2 ": %s\n", \ + __location__, dom_sid_string(mem_ctx, s1), dom_sid_string(mem_ctx, s2));\ + ret = false;\ + } \ +} while (0) + +/* The ~SEC_DESC_SACL_PRESENT is because we don't, as administrator, + * get back the SACL part of the SD when we ask over SAMR */ + +#define TEST_SEC_DESC_EQUAL(sd1, pipe, handle) do {\ + struct sec_desc_buf *sdbuf = samsync_query_ ##pipe## _sec_desc(tctx, mem_ctx, samsync_state, \ + handle); \ + if (!sdbuf || !sdbuf->sd) { \ + torture_comment(tctx, "Could not obtain security descriptor to match " #sd1 "\n");\ + ret = false; \ + } else {\ + if (!security_descriptor_mask_equal(sd1.sd, sdbuf->sd, \ + ~SEC_DESC_SACL_PRESENT)) {\ + torture_comment(tctx, "Security Descriptor Mismatch for %s:\n", #sd1);\ + NDR_PRINT_DEBUG(security_descriptor, sd1.sd);\ + NDR_PRINT_DEBUG(security_descriptor, sdbuf->sd);\ + ret = false;\ + }\ + }\ +} while (0) + +static bool samsync_handle_domain(struct torture_context *tctx, TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + struct netr_DELTA_DOMAIN *domain = delta->delta_union.domain; + struct dom_sid *dom_sid; + struct samr_QueryDomainInfo q[14]; /* q[0] will be unused simple for clarity */ + union samr_DomainInfo *info[14]; + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13}; + int i; + bool ret = true; + + samsync_state->seq_num[database_id] = + domain->sequence_num; + switch (database_id) { + case SAM_DATABASE_DOMAIN: + break; + case SAM_DATABASE_BUILTIN: + if (strcasecmp_m("BUILTIN", domain->domain_name.string) != 0) { + torture_comment(tctx, "BUILTIN domain has different name: %s\n", domain->domain_name.string); + } + break; + case SAM_DATABASE_PRIVS: + torture_comment(tctx, "DOMAIN entry on privs DB!\n"); + return false; + break; + } + + if (!samsync_state->domain_name[database_id]) { + samsync_state->domain_name[database_id] = + talloc_strdup(samsync_state, domain->domain_name.string); + } else { + if (strcasecmp_m(samsync_state->domain_name[database_id], domain->domain_name.string) != 0) { + torture_comment(tctx, "Domain has name varies!: %s != %s\n", samsync_state->domain_name[database_id], + domain->domain_name.string); + return false; + } + } + + if (!samsync_state->domain_handle[database_id]) { + samsync_state->domain_handle[database_id] = + samsync_open_domain(tctx, + samsync_state, + samsync_state, + samsync_state->domain_name[database_id], + &dom_sid); + } + if (samsync_state->domain_handle[database_id]) { + samsync_state->sid[database_id] = dom_sid_dup(samsync_state, dom_sid); + } + + torture_comment(tctx, "\tsequence_nums[%d/%s]=%llu\n", + database_id, domain->domain_name.string, + (long long)samsync_state->seq_num[database_id]); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + q[levels[i]].in.domain_handle = samsync_state->domain_handle[database_id]; + q[levels[i]].in.level = levels[i]; + q[levels[i]].out.info = &info[levels[i]]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_QueryDomainInfo_r(samsync_state->b_samr, mem_ctx, &q[levels[i]]), + talloc_asprintf(tctx, "QueryDomainInfo level %u failed", q[levels[i]].in.level)); + torture_assert_ntstatus_ok(tctx, q[levels[i]].out.result, + talloc_asprintf(tctx, "QueryDomainInfo level %u failed", q[levels[i]].in.level)); + } + + TEST_STRING_EQUAL(info[5]->info5.domain_name, domain->domain_name); + + TEST_STRING_EQUAL(info[2]->general.oem_information, domain->oem_information); + TEST_STRING_EQUAL(info[4]->oem.oem_information, domain->oem_information); + TEST_TIME_EQUAL(info[2]->general.force_logoff_time, domain->force_logoff_time); + TEST_TIME_EQUAL(info[3]->info3.force_logoff_time, domain->force_logoff_time); + + TEST_TIME_EQUAL(info[1]->info1.min_password_length, domain->min_password_length); + TEST_TIME_EQUAL(info[1]->info1.password_history_length, domain->password_history_length); + TEST_TIME_EQUAL(info[1]->info1.max_password_age, domain->max_password_age); + TEST_TIME_EQUAL(info[1]->info1.min_password_age, domain->min_password_age); + + TEST_UINT64_EQUAL(info[8]->info8.sequence_num, + domain->sequence_num); + TEST_TIME_EQUAL(info[8]->info8.domain_create_time, + domain->domain_create_time); + TEST_TIME_EQUAL(info[13]->info13.domain_create_time, + domain->domain_create_time); + + TEST_SEC_DESC_EQUAL(domain->sdbuf, samr, samsync_state->domain_handle[database_id]); + + return ret; +} + +static bool samsync_handle_policy(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + struct netr_DELTA_POLICY *policy = delta->delta_union.policy; + + switch (database_id) { + case SAM_DATABASE_DOMAIN: + case SAM_DATABASE_BUILTIN: + break; + case SAM_DATABASE_PRIVS: + torture_comment(tctx, "DOMAIN entry on privs DB!\n"); + return false; + } + + samsync_state->seq_num[database_id] = + policy->sequence_num; + + if (!samsync_state->domain_name[SAM_DATABASE_DOMAIN]) { + samsync_state->domain_name[SAM_DATABASE_DOMAIN] = + talloc_strdup(samsync_state, policy->primary_domain_name.string); + } else { + if (strcasecmp_m(samsync_state->domain_name[SAM_DATABASE_DOMAIN], policy->primary_domain_name.string) != 0) { + torture_comment(tctx, "PRIMARY domain has name varies between DOMAIN and POLICY!: %s != %s\n", samsync_state->domain_name[SAM_DATABASE_DOMAIN], + policy->primary_domain_name.string); + return false; + } + } + + if (!dom_sid_equal(samsync_state->sid[SAM_DATABASE_DOMAIN], policy->sid)) { + torture_comment(tctx, "Domain SID from POLICY (%s) does not match domain sid from SAMR (%s)\n", + dom_sid_string(mem_ctx, policy->sid), dom_sid_string(mem_ctx, samsync_state->sid[SAM_DATABASE_DOMAIN])); + return false; + } + + torture_comment(tctx, "\tsequence_nums[%d/PRIVS]=%llu\n", + database_id, + (long long)samsync_state->seq_num[database_id]); + return true; +} + +static bool samsync_handle_user(struct torture_context *tctx, TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_USER *user = delta->delta_union.user; + struct netr_SamInfo3 *info3 = NULL; + struct samr_Password lm_hash; + struct samr_Password nt_hash; + struct samr_Password *lm_hash_p = NULL; + struct samr_Password *nt_hash_p = NULL; + const char *domain; + const char *username = user->account_name.string; + NTSTATUS nt_status; + bool ret = true; + struct samr_OpenUser r; + struct samr_QueryUserInfo q; + union samr_UserInfo *info; + struct policy_handle user_handle; + struct samr_GetGroupsForUser getgr; + struct samr_RidWithAttributeArray *rids; + + switch (database_id) { + case SAM_DATABASE_DOMAIN: + case SAM_DATABASE_BUILTIN: + break; + case SAM_DATABASE_PRIVS: + torture_comment(tctx, "DOMAIN entry on privs DB!\n"); + return false; + } + + domain = samsync_state->domain_name[database_id]; + + if (domain == NULL || + samsync_state->domain_handle[database_id] == NULL) { + torture_comment(tctx, "SamSync needs domain information before the users\n"); + return false; + } + + r.in.domain_handle = samsync_state->domain_handle[database_id]; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.user_handle = &user_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenUser_r(samsync_state->b_samr, mem_ctx, &r), + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + torture_assert_ntstatus_ok(tctx, r.out.result, + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + + q.in.user_handle = &user_handle; + q.in.level = 21; + q.out.info = &info; + + TEST_SEC_DESC_EQUAL(user->sdbuf, samr, &user_handle); + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_QueryUserInfo_r(samsync_state->b_samr, mem_ctx, &q), + talloc_asprintf(tctx, "OpenUserInfo level %u failed", q.in.level)); + torture_assert_ntstatus_ok(tctx, q.out.result, + talloc_asprintf(tctx, "OpenUserInfo level %u failed", q.in.level)); + + getgr.in.user_handle = &user_handle; + getgr.out.rids = &rids; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_GetGroupsForUser_r(samsync_state->b_samr, mem_ctx, &getgr), + "GetGroupsForUser failed"); + torture_assert_ntstatus_ok(tctx, getgr.out.result, + "GetGroupsForUser failed"); + + if (!test_samr_handle_Close(samsync_state->b_samr, tctx, &user_handle)) { + torture_comment(tctx, "samr_handle_Close failed\n"); + ret = false; + } + if (!ret) { + return false; + } + + TEST_STRING_EQUAL(info->info21.account_name, user->account_name); + TEST_STRING_EQUAL(info->info21.full_name, user->full_name); + TEST_INT_EQUAL(info->info21.rid, user->rid); + TEST_INT_EQUAL(info->info21.primary_gid, user->primary_gid); + TEST_STRING_EQUAL(info->info21.home_directory, user->home_directory); + TEST_STRING_EQUAL(info->info21.home_drive, user->home_drive); + TEST_STRING_EQUAL(info->info21.logon_script, user->logon_script); + TEST_STRING_EQUAL(info->info21.description, user->description); + TEST_STRING_EQUAL(info->info21.workstations, user->workstations); + + TEST_TIME_EQUAL(info->info21.last_logon, user->last_logon); + TEST_TIME_EQUAL(info->info21.last_logoff, user->last_logoff); + + + TEST_INT_EQUAL(info->info21.logon_hours.units_per_week, + user->logon_hours.units_per_week); + if (ret) { + if (memcmp(info->info21.logon_hours.bits, user->logon_hours.bits, + info->info21.logon_hours.units_per_week/8) != 0) { + torture_comment(tctx, "Logon hours mismatch\n"); + ret = false; + } + } + + TEST_INT_EQUAL(info->info21.bad_password_count, + user->bad_password_count); + TEST_INT_EQUAL(info->info21.logon_count, + user->logon_count); + + TEST_TIME_EQUAL(info->info21.last_password_change, + user->last_password_change); + TEST_TIME_EQUAL(info->info21.acct_expiry, + user->acct_expiry); + + TEST_INT_EQUAL((info->info21.acct_flags & ~ACB_PW_EXPIRED), user->acct_flags); + if (user->acct_flags & ACB_PWNOEXP) { + if (info->info21.acct_flags & ACB_PW_EXPIRED) { + torture_comment(tctx, "ACB flags mismatch: both expired and no expiry!\n"); + ret = false; + } + if (info->info21.force_password_change != (NTTIME)0x7FFFFFFFFFFFFFFFULL) { + torture_comment(tctx, "ACB flags mismatch: no password expiry, but force password change 0x%016llx (%lld) != 0x%016llx (%lld)\n", + (unsigned long long)info->info21.force_password_change, + (unsigned long long)info->info21.force_password_change, + (unsigned long long)0x7FFFFFFFFFFFFFFFULL, (unsigned long long)0x7FFFFFFFFFFFFFFFULL + ); + ret = false; + } + } + + TEST_INT_EQUAL(info->info21.nt_password_set, user->nt_password_present); + TEST_INT_EQUAL(info->info21.lm_password_set, user->lm_password_present); + TEST_INT_EQUAL(info->info21.password_expired, user->password_expired); + + TEST_STRING_EQUAL(info->info21.comment, user->comment); + TEST_BINARY_STRING_EQUAL(info->info21.parameters, user->parameters); + + TEST_INT_EQUAL(info->info21.country_code, user->country_code); + TEST_INT_EQUAL(info->info21.code_page, user->code_page); + + TEST_STRING_EQUAL(info->info21.profile_path, user->profile_path); + + if (user->lm_password_present) { + lm_hash_p = &lm_hash; + } + if (user->nt_password_present) { + nt_hash_p = &nt_hash; + } + + if (user->user_private_info.SensitiveData) { + DATA_BLOB data; + struct netr_USER_KEYS keys; + enum ndr_err_code ndr_err; + data.data = user->user_private_info.SensitiveData; + data.length = user->user_private_info.DataLength; + ndr_err = ndr_pull_struct_blob(&data, mem_ctx, &keys, (ndr_pull_flags_fn_t)ndr_pull_netr_USER_KEYS); + if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + if (keys.keys.keys2.lmpassword.length == 16) { + lm_hash_p = &lm_hash; + } + if (keys.keys.keys2.ntpassword.length == 16) { + nt_hash_p = &nt_hash; + } + } else { + torture_comment(tctx, "Failed to parse Sensitive Data for %s:\n", username); +#if 0 + dump_data(0, data.data, data.length); +#endif + return false; + } + } + + if (nt_hash_p) { + DATA_BLOB nt_hash_blob = data_blob_const(nt_hash_p, 16); + DEBUG(100,("ACCOUNT [%s\\%-25s] NTHASH %s\n", samsync_state->domain_name[0], username, data_blob_hex_string_upper(mem_ctx, &nt_hash_blob))); + } + if (lm_hash_p) { + DATA_BLOB lm_hash_blob = data_blob_const(lm_hash_p, 16); + DEBUG(100,("ACCOUNT [%s\\%-25s] LMHASH %s\n", samsync_state->domain_name[0], username, data_blob_hex_string_upper(mem_ctx, &lm_hash_blob))); + } + + nt_status = test_SamLogon(tctx, + samsync_state->p_netlogon_wksta, mem_ctx, samsync_state->creds_netlogon_wksta, + domain, + username, + TEST_WKSTA_MACHINE_NAME, + lm_hash_p, + nt_hash_p, + &info3); + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) { + if (user->acct_flags & ACB_DISABLED) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT)) { + if (user->acct_flags & ACB_WSTRUST) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT)) { + if (user->acct_flags & ACB_SVRTRUST) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT)) { + if (user->acct_flags & ACB_DOMTRUST) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT)) { + if (user->acct_flags & ACB_DOMTRUST) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + if (user->acct_flags & ACB_AUTOLOCK) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_EXPIRED)) { + if (info->info21.acct_flags & ACB_PW_EXPIRED) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) { + if (!lm_hash_p && !nt_hash_p) { + return true; + } + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_PASSWORD_MUST_CHANGE)) { + /* We would need to know the server's current time to test this properly */ + return true; + } else if (NT_STATUS_IS_OK(nt_status)) { + TEST_INT_EQUAL(user->rid, info3->base.rid); + TEST_INT_EQUAL(user->primary_gid, info3->base.primary_gid); + /* this is 0x0 from NT4 sp6 */ + if (info3->base.acct_flags) { + TEST_INT_EQUAL(user->acct_flags, info3->base.acct_flags); + } + /* this is NULL from NT4 sp6 */ + if (info3->base.account_name.string) { + TEST_STRING_EQUAL(user->account_name, info3->base.account_name); + } + /* this is NULL from Win2k3 */ + if (info3->base.full_name.string) { + TEST_STRING_EQUAL(user->full_name, info3->base.full_name); + } + TEST_STRING_EQUAL(user->logon_script, info3->base.logon_script); + TEST_STRING_EQUAL(user->profile_path, info3->base.profile_path); + TEST_STRING_EQUAL(user->home_directory, info3->base.home_directory); + TEST_STRING_EQUAL(user->home_drive, info3->base.home_drive); + TEST_STRING_EQUAL(user->logon_script, info3->base.logon_script); + + + TEST_TIME_EQUAL(user->last_logon, info3->base.logon_time); + TEST_TIME_EQUAL(user->acct_expiry, info3->base.kickoff_time); + TEST_TIME_EQUAL(user->last_password_change, info3->base.last_password_change); + TEST_TIME_EQUAL(info->info21.force_password_change, info3->base.force_password_change); + + /* Does the concept of a logoff time ever really + * exist? (not in any sensible way, according to the + * doco I read -- abartlet) */ + + /* This copes with the two different versions of 0 I see */ + /* with NT4 sp6 we have the || case */ + if (!((user->last_logoff == 0) + || (info3->base.logoff_time == 0x7fffffffffffffffLL))) { + TEST_TIME_EQUAL(user->last_logoff, info3->base.logoff_time); + } + + TEST_INT_EQUAL(rids->count, info3->base.groups.count); + if (rids->count == info3->base.groups.count) { + int i, j; + int count = rids->count; + bool *matched = talloc_zero_array(mem_ctx, bool, rids->count); + + for (i = 0; i < count; i++) { + for (j = 0; j < count; j++) { + if ((rids->rids[i].rid == + info3->base.groups.rids[j].rid) + && (rids->rids[i].attributes == + info3->base.groups.rids[j].attributes)) { + matched[i] = true; + } + } + } + + for (i = 0; i < rids->count; i++) { + if (matched[i] == false) { + ret = false; + torture_comment(tctx, "Could not find group RID %u found in getgroups in NETLOGON reply\n", + rids->rids[i].rid); + } + } + } + return ret; + } else { + torture_comment(tctx, "Could not validate password for user %s\\%s: %s\n", + domain, username, nt_errstr(nt_status)); + return false; + } + return false; +} + +static bool samsync_handle_alias(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_ALIAS *alias = delta->delta_union.alias; + bool ret = true; + + struct samr_OpenAlias r; + struct samr_QueryAliasInfo q; + union samr_AliasInfo *info; + struct policy_handle alias_handle; + + switch (database_id) { + case SAM_DATABASE_DOMAIN: + case SAM_DATABASE_BUILTIN: + break; + case SAM_DATABASE_PRIVS: + torture_comment(tctx, "DOMAIN entry on privs DB!\n"); + return false; + } + + if (samsync_state->domain_name[database_id] == NULL || + samsync_state->domain_handle[database_id] == NULL) { + torture_comment(tctx, "SamSync needs domain information before the users\n"); + return false; + } + + r.in.domain_handle = samsync_state->domain_handle[database_id]; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.alias_handle = &alias_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenAlias_r(samsync_state->b_samr, mem_ctx, &r), + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + torture_assert_ntstatus_ok(tctx, r.out.result, + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + + q.in.alias_handle = &alias_handle; + q.in.level = 1; + q.out.info = &info; + + TEST_SEC_DESC_EQUAL(alias->sdbuf, samr, &alias_handle); + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_QueryAliasInfo_r(samsync_state->b_samr, mem_ctx, &q), + "QueryAliasInfo failed"); + if (!test_samr_handle_Close(samsync_state->b_samr, tctx, &alias_handle)) { + return false; + } + + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_comment(tctx, "QueryAliasInfo level %u failed - %s\n", + q.in.level, nt_errstr(q.out.result)); + return false; + } + + TEST_STRING_EQUAL(info->all.name, alias->alias_name); + TEST_STRING_EQUAL(info->all.description, alias->description); + return ret; +} + +static bool samsync_handle_group(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_GROUP *group = delta->delta_union.group; + bool ret = true; + + struct samr_OpenGroup r; + struct samr_QueryGroupInfo q; + union samr_GroupInfo *info; + struct policy_handle group_handle; + + switch (database_id) { + case SAM_DATABASE_DOMAIN: + case SAM_DATABASE_BUILTIN: + break; + case SAM_DATABASE_PRIVS: + torture_comment(tctx, "DOMAIN entry on privs DB!\n"); + return false; + } + + if (samsync_state->domain_name[database_id] == NULL || + samsync_state->domain_handle[database_id] == NULL) { + torture_comment(tctx, "SamSync needs domain information before the users\n"); + return false; + } + + r.in.domain_handle = samsync_state->domain_handle[database_id]; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.group_handle = &group_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenGroup_r(samsync_state->b_samr, mem_ctx, &r), + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + torture_assert_ntstatus_ok(tctx, r.out.result, + talloc_asprintf(tctx, "OpenUser(%u) failed", rid)); + + q.in.group_handle = &group_handle; + q.in.level = 1; + q.out.info = &info; + + TEST_SEC_DESC_EQUAL(group->sdbuf, samr, &group_handle); + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_QueryGroupInfo_r(samsync_state->b_samr, mem_ctx, &q), + "QueryGroupInfo failed"); + if (!test_samr_handle_Close(samsync_state->b_samr, tctx, &group_handle)) { + return false; + } + + if (!NT_STATUS_IS_OK(q.out.result)) { + torture_comment(tctx, "QueryGroupInfo level %u failed - %s\n", + q.in.level, nt_errstr(q.out.result)); + return false; + } + + TEST_STRING_EQUAL(info->all.name, group->group_name); + TEST_INT_EQUAL(info->all.attributes, group->attributes); + TEST_STRING_EQUAL(info->all.description, group->description); + return ret; +} + +static bool samsync_handle_secret(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + struct netr_DELTA_SECRET *secret = delta->delta_union.secret; + const char *name = delta->delta_id_union.name; + struct samsync_secret *nsec = talloc(samsync_state, struct samsync_secret); + struct samsync_secret *old = talloc(mem_ctx, struct samsync_secret); + struct lsa_QuerySecret q; + struct lsa_OpenSecret o; + struct policy_handle sec_handle; + struct lsa_DATA_BUF_PTR bufp1; + struct lsa_DATA_BUF_PTR bufp2; + NTTIME nsec_mtime; + NTTIME old_mtime; + bool ret = true; + DATA_BLOB lsa_blob1, lsa_blob_out, session_key; + NTSTATUS status; + + nsec->name = talloc_strdup(nsec, name); + nsec->secret = data_blob_talloc(nsec, secret->current_cipher.cipher_data, secret->current_cipher.maxlen); + nsec->mtime = secret->current_cipher_set_time; + + DLIST_ADD(samsync_state->secrets, nsec); + + old->name = talloc_strdup(old, name); + old->secret = data_blob_const(secret->old_cipher.cipher_data, secret->old_cipher.maxlen); + old->mtime = secret->old_cipher_set_time; + + o.in.handle = samsync_state->lsa_handle; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.in.name.string = name; + o.out.sec_handle = &sec_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_OpenSecret_r(samsync_state->b_lsa, mem_ctx, &o), + "OpenSecret failed"); + torture_assert_ntstatus_ok(tctx, o.out.result, + "OpenSecret failed"); + +/* + We would like to do this, but it is NOT_SUPPORTED on win2k3 + TEST_SEC_DESC_EQUAL(secret->sdbuf, lsa, &sec_handle); +*/ + status = dcerpc_fetch_session_key(samsync_state->p_lsa, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "dcerpc_fetch_session_key failed - %s\n", nt_errstr(status)); + return false; + } + + + ZERO_STRUCT(nsec_mtime); + ZERO_STRUCT(old_mtime); + + /* fetch the secret back again */ + q.in.sec_handle = &sec_handle; + q.in.new_val = &bufp1; + q.in.new_mtime = &nsec_mtime; + q.in.old_val = &bufp2; + q.in.old_mtime = &old_mtime; + + bufp1.buf = NULL; + bufp2.buf = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_QuerySecret_r(samsync_state->b_lsa, mem_ctx, &q), + "QuerySecret failed"); + if (NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, q.out.result)) { + /* some things are just off limits */ + return true; + } else if (!NT_STATUS_IS_OK(q.out.result)) { + torture_comment(tctx, "QuerySecret failed - %s\n", nt_errstr(q.out.result)); + return false; + } + + if (q.out.old_val->buf == NULL) { + /* probably just not available due to ACLs */ + } else { + lsa_blob1.data = q.out.old_val->buf->data; + lsa_blob1.length = q.out.old_val->buf->length; + + status = sess_decrypt_blob(mem_ctx, &lsa_blob1, &session_key, &lsa_blob_out); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to decrypt secrets OLD blob: %s\n", nt_errstr(status)); + return false; + } + + if (!q.out.old_mtime) { + torture_comment(tctx, "OLD mtime not available on LSA for secret %s\n", old->name); + ret = false; + } + if (old->mtime != *q.out.old_mtime) { + torture_comment(tctx, "OLD mtime on secret %s does not match between SAMSYNC (%s) and LSA (%s)\n", + old->name, nt_time_string(mem_ctx, old->mtime), + nt_time_string(mem_ctx, *q.out.old_mtime)); + ret = false; + } + + if (old->secret.length != lsa_blob_out.length) { + torture_comment(tctx, "Returned secret %s doesn't match: %d != %d\n", + old->name, (int)old->secret.length, (int)lsa_blob_out.length); + ret = false; + } else if (memcmp(lsa_blob_out.data, + old->secret.data, old->secret.length) != 0) { + torture_comment(tctx, "Returned secret %s doesn't match: \n", + old->name); + DEBUG(1, ("SamSync Secret:\n")); + dump_data(1, old->secret.data, old->secret.length); + DEBUG(1, ("LSA Secret:\n")); + dump_data(1, lsa_blob_out.data, lsa_blob_out.length); + ret = false; + } + + } + + if (q.out.new_val->buf == NULL) { + /* probably just not available due to ACLs */ + } else { + lsa_blob1.data = q.out.new_val->buf->data; + lsa_blob1.length = q.out.new_val->buf->length; + + status = sess_decrypt_blob(mem_ctx, &lsa_blob1, &session_key, &lsa_blob_out); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "Failed to decrypt secrets OLD blob\n"); + return false; + } + + if (!q.out.new_mtime) { + torture_comment(tctx, "NEW mtime not available on LSA for secret %s\n", nsec->name); + ret = false; + } + if (nsec->mtime != *q.out.new_mtime) { + torture_comment(tctx, "NEW mtime on secret %s does not match between SAMSYNC (%s) and LSA (%s)\n", + nsec->name, nt_time_string(mem_ctx, nsec->mtime), + nt_time_string(mem_ctx, *q.out.new_mtime)); + ret = false; + } + + if (nsec->secret.length != lsa_blob_out.length) { + torture_comment(tctx, "Returned secret %s doesn't match: %d != %d\n", + nsec->name, (int)nsec->secret.length, (int)lsa_blob_out.length); + ret = false; + } else if (memcmp(lsa_blob_out.data, + nsec->secret.data, nsec->secret.length) != 0) { + torture_comment(tctx, "Returned secret %s doesn't match: \n", + nsec->name); + DEBUG(1, ("SamSync Secret:\n")); + dump_data(1, nsec->secret.data, nsec->secret.length); + DEBUG(1, ("LSA Secret:\n")); + dump_data(1, lsa_blob_out.data, lsa_blob_out.length); + ret = false; + } + } + + return ret; +} + +static bool samsync_handle_trusted_domain(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + bool ret = true; + struct netr_DELTA_TRUSTED_DOMAIN *trusted_domain = delta->delta_union.trusted_domain; + struct dom_sid *dom_sid = delta->delta_id_union.sid; + + struct samsync_trusted_domain *ndom = talloc(samsync_state, struct samsync_trusted_domain); + struct lsa_OpenTrustedDomain t; + struct policy_handle trustdom_handle; + struct lsa_QueryTrustedDomainInfo q; + union lsa_TrustedDomainInfo *info[9]; + union lsa_TrustedDomainInfo *_info = NULL; + int levels [] = {1, 3, 8}; + int i; + + ndom->name = talloc_strdup(ndom, trusted_domain->domain_name.string); + ndom->sid = dom_sid_dup(ndom, dom_sid); + + t.in.handle = samsync_state->lsa_handle; + t.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + t.in.sid = dom_sid; + t.out.trustdom_handle = &trustdom_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_OpenTrustedDomain_r(samsync_state->b_lsa, mem_ctx, &t), + "OpenTrustedDomain failed"); + torture_assert_ntstatus_ok(tctx, t.out.result, + "OpenTrustedDomain failed"); + + for (i=0; i< ARRAY_SIZE(levels); i++) { + q.in.trustdom_handle = &trustdom_handle; + q.in.level = levels[i]; + q.out.info = &_info; + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_QueryTrustedDomainInfo_r(samsync_state->b_lsa, mem_ctx, &q), + "QueryTrustedDomainInfo failed"); + if (!NT_STATUS_IS_OK(q.out.result)) { + if (q.in.level == 8 && NT_STATUS_EQUAL(q.out.result, NT_STATUS_INVALID_PARAMETER)) { + info[levels[i]] = NULL; + continue; + } + torture_comment(tctx, "QueryInfoTrustedDomain level %d failed - %s\n", + levels[i], nt_errstr(q.out.result)); + return false; + } + info[levels[i]] = _info; + } + + if (info[8]) { + TEST_SID_EQUAL(info[8]->full_info.info_ex.sid, dom_sid); + TEST_STRING_EQUAL(info[8]->full_info.info_ex.netbios_name, trusted_domain->domain_name); + } + TEST_STRING_EQUAL(info[1]->name.netbios_name, trusted_domain->domain_name); + TEST_INT_EQUAL(info[3]->posix_offset.posix_offset, trusted_domain->posix_offset); +/* + We would like to do this, but it is NOT_SUPPORTED on win2k3 + TEST_SEC_DESC_EQUAL(trusted_domain->sdbuf, lsa, &trustdom_handle); +*/ + DLIST_ADD(samsync_state->trusted_domains, ndom); + + return ret; +} + +static bool samsync_handle_account(struct torture_context *tctx, + TALLOC_CTX *mem_ctx, struct samsync_state *samsync_state, + int database_id, struct netr_DELTA_ENUM *delta) +{ + bool ret = true; + struct netr_DELTA_ACCOUNT *account = delta->delta_union.account; + struct dom_sid *dom_sid = delta->delta_id_union.sid; + + struct lsa_OpenAccount a; + struct policy_handle acct_handle; + struct lsa_EnumPrivsAccount e; + struct lsa_PrivilegeSet *privs = NULL; + struct lsa_LookupPrivName r; + + int i, j; + + bool *found_priv_in_lsa; + + a.in.handle = samsync_state->lsa_handle; + a.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + a.in.sid = dom_sid; + a.out.acct_handle = &acct_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_OpenAccount_r(samsync_state->b_lsa, mem_ctx, &a), + "OpenAccount failed"); + torture_assert_ntstatus_ok(tctx, a.out.result, + "OpenAccount failed"); + + TEST_SEC_DESC_EQUAL(account->sdbuf, lsa, &acct_handle); + + found_priv_in_lsa = talloc_zero_array(mem_ctx, bool, account->privilege_entries); + + e.in.handle = &acct_handle; + e.out.privs = &privs; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_EnumPrivsAccount_r(samsync_state->b_lsa, mem_ctx, &e), + "EnumPrivsAccount failed"); + torture_assert_ntstatus_ok(tctx, e.out.result, + "EnumPrivsAccount failed"); + + if ((account->privilege_entries && !privs)) { + torture_comment(tctx, "Account %s has privileges in SamSync, but not LSA\n", + dom_sid_string(mem_ctx, dom_sid)); + return false; + } + + if (!account->privilege_entries && privs && privs->count) { + torture_comment(tctx, "Account %s has privileges in LSA, but not SamSync\n", + dom_sid_string(mem_ctx, dom_sid)); + return false; + } + + TEST_INT_EQUAL(account->privilege_entries, privs->count); + + for (i=0;i< privs->count; i++) { + + struct lsa_StringLarge *name = NULL; + + r.in.handle = samsync_state->lsa_handle; + r.in.luid = &privs->set[i].luid; + r.out.name = &name; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_LookupPrivName_r(samsync_state->b_lsa, mem_ctx, &r), + "\nLookupPrivName failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "\nLookupPrivName failed"); + + if (!r.out.name) { + torture_comment(tctx, "\nLookupPrivName failed to return a name\n"); + return false; + } + for (j=0;j<account->privilege_entries; j++) { + if (strcmp(name->string, account->privilege_name[j].string) == 0) { + found_priv_in_lsa[j] = true; + break; + } + } + } + for (j=0;j<account->privilege_entries; j++) { + if (!found_priv_in_lsa[j]) { + torture_comment(tctx, "Privilege %s on account %s not found in LSA\n", account->privilege_name[j].string, + dom_sid_string(mem_ctx, dom_sid)); + ret = false; + } + } + return ret; +} + +/* + try a netlogon DatabaseSync +*/ +static bool test_DatabaseSync(struct torture_context *tctx, + struct samsync_state *samsync_state, + TALLOC_CTX *mem_ctx) +{ + TALLOC_CTX *loop_ctx, *delta_ctx, *trustdom_ctx; + struct netr_DatabaseSync r; + const enum netr_SamDatabaseID database_ids[] = {SAM_DATABASE_DOMAIN, SAM_DATABASE_BUILTIN, SAM_DATABASE_PRIVS}; + int i, d; + bool ret = true; + struct samsync_trusted_domain *t; + struct samsync_secret *s; + struct netr_Authenticator return_authenticator, credential; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + + const char *domain, *username; + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(mem_ctx, "\\\\%s", dcerpc_server_name(samsync_state->p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.preferredmaximumlength = (uint32_t)-1; + r.in.return_authenticator = &return_authenticator; + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + + uint32_t sync_context = 0; + + r.in.database_id = database_ids[i]; + r.in.sync_context = &sync_context; + r.out.sync_context = &sync_context; + + torture_comment(tctx, "Testing DatabaseSync of id %d\n", r.in.database_id); + + do { + loop_ctx = talloc_named(mem_ctx, 0, "DatabaseSync loop context"); + netlogon_creds_client_authenticator(samsync_state->creds, &credential); + + r.in.credential = &credential; + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_DatabaseSync_r(samsync_state->b, loop_ctx, &r), + "DatabaseSync failed"); + if (!NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) { + torture_comment(tctx, "DatabaseSync - %s\n", nt_errstr(r.out.result)); + ret = false; + break; + } + + if (!netlogon_creds_client_check(samsync_state->creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + r.in.sync_context = r.out.sync_context; + + for (d=0; d < delta_enum_array->num_deltas; d++) { + delta_ctx = talloc_named(loop_ctx, 0, "DatabaseSync delta context"); + + if (!NT_STATUS_IS_OK(samsync_fix_delta(delta_ctx, samsync_state->creds, + r.in.database_id, + &delta_enum_array->delta_enum[d]))) { + torture_comment(tctx, "Failed to decrypt delta\n"); + ret = false; + } + + switch (delta_enum_array->delta_enum[d].delta_type) { + case NETR_DELTA_DOMAIN: + if (!samsync_handle_domain(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_DOMAIN\n"); + ret = false; + } + break; + case NETR_DELTA_GROUP: + if (!samsync_handle_group(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_USER\n"); + ret = false; + } + break; + case NETR_DELTA_USER: + if (!samsync_handle_user(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_USER\n"); + ret = false; + } + break; + case NETR_DELTA_ALIAS: + if (!samsync_handle_alias(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_ALIAS\n"); + ret = false; + } + break; + case NETR_DELTA_POLICY: + if (!samsync_handle_policy(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_POLICY\n"); + ret = false; + } + break; + case NETR_DELTA_TRUSTED_DOMAIN: + if (!samsync_handle_trusted_domain(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_TRUSTED_DOMAIN\n"); + ret = false; + } + break; + case NETR_DELTA_ACCOUNT: + if (!samsync_handle_account(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_ACCOUNT\n"); + ret = false; + } + break; + case NETR_DELTA_SECRET: + if (!samsync_handle_secret(tctx, delta_ctx, samsync_state, + r.in.database_id, &delta_enum_array->delta_enum[d])) { + torture_comment(tctx, "Failed to handle DELTA_SECRET\n"); + ret = false; + } + break; + case NETR_DELTA_GROUP_MEMBER: + case NETR_DELTA_ALIAS_MEMBER: + /* These are harder to cross-check, and we expect them */ + break; + case NETR_DELTA_DELETE_GROUP: + case NETR_DELTA_RENAME_GROUP: + case NETR_DELTA_DELETE_USER: + case NETR_DELTA_RENAME_USER: + case NETR_DELTA_DELETE_ALIAS: + case NETR_DELTA_RENAME_ALIAS: + case NETR_DELTA_DELETE_TRUST: + case NETR_DELTA_DELETE_ACCOUNT: + case NETR_DELTA_DELETE_SECRET: + case NETR_DELTA_DELETE_GROUP2: + case NETR_DELTA_DELETE_USER2: + case NETR_DELTA_MODIFY_COUNT: + default: + torture_comment(tctx, "Uxpected delta type %d\n", delta_enum_array->delta_enum[d].delta_type); + ret = false; + break; + } + talloc_free(delta_ctx); + } + talloc_free(loop_ctx); + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + + } + + domain = samsync_state->domain_name[SAM_DATABASE_DOMAIN]; + if (!domain) { + torture_comment(tctx, "Never got a DOMAIN object in samsync!\n"); + return false; + } + + trustdom_ctx = talloc_named(mem_ctx, 0, "test_DatabaseSync Trusted domains context"); + + username = talloc_asprintf(trustdom_ctx, "%s$", domain); + for (t=samsync_state->trusted_domains; t; t=t->next) { + char *secret_name = talloc_asprintf(trustdom_ctx, "G$$%s", t->name); + for (s=samsync_state->secrets; s; s=s->next) { + if (strcasecmp_m(s->name, secret_name) == 0) { + NTSTATUS nt_status; + struct samr_Password nt_hash; + mdfour(nt_hash.hash, s->secret.data, s->secret.length); + + torture_comment(tctx, "Checking password for %s\\%s\n", t->name, username); + nt_status = test_SamLogon(tctx, + samsync_state->p_netlogon_wksta, trustdom_ctx, samsync_state->creds_netlogon_wksta, + t->name, + username, + TEST_WKSTA_MACHINE_NAME, + NULL, + &nt_hash, + NULL); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_LOGON_SERVERS)) { + torture_comment(tctx, "Verifiction of trust password to %s failed: %s (the trusted domain is not available)\n", + t->name, nt_errstr(nt_status)); + + break; + } + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT)) { + torture_comment(tctx, "Verifiction of trust password to %s: should have failed (nologon interdomain trust account), instead: %s\n", + t->name, nt_errstr(nt_status)); + ret = false; + } + + /* break it */ + nt_hash.hash[0]++; + nt_status = test_SamLogon(tctx, + samsync_state->p_netlogon_wksta, trustdom_ctx, samsync_state->creds_netlogon_wksta, + t->name, + username, + TEST_WKSTA_MACHINE_NAME, + NULL, + &nt_hash, + NULL); + + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) { + torture_comment(tctx, "Verifiction of trust password to %s: should have failed (wrong password), instead: %s\n", + t->name, nt_errstr(nt_status)); + ret = false; + } + + break; + } + } + } + talloc_free(trustdom_ctx); + return ret; +} + + +/* + try a netlogon DatabaseDeltas +*/ +static bool test_DatabaseDeltas(struct torture_context *tctx, + struct samsync_state *samsync_state, TALLOC_CTX *mem_ctx) +{ + TALLOC_CTX *loop_ctx; + struct netr_DatabaseDeltas r; + struct netr_Authenticator credential; + struct netr_Authenticator return_authenticator; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + const uint32_t database_ids[] = {0, 1, 2}; + int i; + bool ret = true; + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(mem_ctx, "\\\\%s", dcerpc_server_name(samsync_state->p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.preferredmaximumlength = (uint32_t)-1; + r.in.return_authenticator = &return_authenticator; + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + + uint64_t seq_num = samsync_state->seq_num[i]; + + r.in.database_id = database_ids[i]; + r.in.sequence_num = &seq_num; + r.out.sequence_num = &seq_num; + + if (seq_num == 0) continue; + + /* this shows that the bdc doesn't need to do a single call for + * each seqnumber, and the pdc doesn't need to know about old values + * -- metze + */ + seq_num -= 10; + + torture_comment(tctx, "Testing DatabaseDeltas of id %d at %llu\n", + r.in.database_id, (long long)seq_num); + + do { + loop_ctx = talloc_named(mem_ctx, 0, "test_DatabaseDeltas loop context"); + netlogon_creds_client_authenticator(samsync_state->creds, &credential); + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_DatabaseDeltas_r(samsync_state->b, loop_ctx, &r), + "DatabaseDeltas failed"); + if (!NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES) && + !NT_STATUS_EQUAL(r.out.result, NT_STATUS_SYNCHRONIZATION_REQUIRED)) { + torture_comment(tctx, "DatabaseDeltas - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + if (!netlogon_creds_client_check(samsync_state->creds, &return_authenticator.cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + seq_num++; + talloc_free(loop_ctx); + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return ret; +} + + +/* + try a netlogon DatabaseSync2 +*/ +static bool test_DatabaseSync2(struct torture_context *tctx, + struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds) +{ + TALLOC_CTX *loop_ctx; + struct netr_DatabaseSync2 r; + const uint32_t database_ids[] = {0, 1, 2}; + int i; + bool ret = true; + struct netr_Authenticator return_authenticator, credential; + struct netr_DELTA_ENUM_ARRAY *delta_enum_array = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(return_authenticator); + + r.in.logon_server = talloc_asprintf(mem_ctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computername = TEST_MACHINE_NAME; + r.in.preferredmaximumlength = (uint32_t)-1; + r.in.return_authenticator = &return_authenticator; + r.out.return_authenticator = &return_authenticator; + r.out.delta_enum_array = &delta_enum_array; + + for (i=0;i<ARRAY_SIZE(database_ids);i++) { + + uint32_t sync_context = 0; + + r.in.database_id = database_ids[i]; + r.in.sync_context = &sync_context; + r.out.sync_context = &sync_context; + r.in.restart_state = 0; + + torture_comment(tctx, "Testing DatabaseSync2 of id %d\n", r.in.database_id); + + do { + loop_ctx = talloc_named(mem_ctx, 0, "test_DatabaseSync2 loop context"); + netlogon_creds_client_authenticator(creds, &credential); + + r.in.credential = &credential; + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_DatabaseSync2_r(b, loop_ctx, &r), + "DatabaseSync2 failed"); + if (!NT_STATUS_IS_OK(r.out.result) && + !NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)) { + torture_comment(tctx, "DatabaseSync2 - %s\n", nt_errstr(r.out.result)); + ret = false; + } + + if (!netlogon_creds_client_check(creds, &r.out.return_authenticator->cred)) { + torture_comment(tctx, "Credential chaining failed\n"); + } + + talloc_free(loop_ctx); + } while (NT_STATUS_EQUAL(r.out.result, STATUS_MORE_ENTRIES)); + } + + return ret; +} + + + +bool torture_rpc_samsync(struct torture_context *torture) +{ + NTSTATUS status; + TALLOC_CTX *mem_ctx; + bool ret = true; + struct test_join *join_ctx; + struct test_join *join_ctx2; + struct test_join *user_ctx; + const char *machine_password; + const char *wksta_machine_password; + struct dcerpc_binding *b; + struct dcerpc_binding *b_netlogon_wksta; + struct samr_Connect c; + struct samr_SetDomainInfo s; + struct policy_handle *domain_policy; + + struct lsa_ObjectAttribute attr; + struct lsa_QosInfo qos; + struct lsa_OpenPolicy2 r; + struct cli_credentials *credentials; + struct cli_credentials *credentials_wksta; + + struct samsync_state *samsync_state; + + char *test_machine_account; + + char *test_wksta_machine_account; + + mem_ctx = talloc_init("torture_rpc_netlogon"); + + test_machine_account = talloc_asprintf(mem_ctx, "%s$", TEST_MACHINE_NAME); + join_ctx = torture_create_testuser(torture, test_machine_account, + lpcfg_workgroup(torture->lp_ctx), ACB_SVRTRUST, + &machine_password); + if (!join_ctx) { + talloc_free(mem_ctx); + torture_comment(torture, "Failed to join as BDC\n"); + return false; + } + + test_wksta_machine_account = talloc_asprintf(mem_ctx, "%s$", TEST_WKSTA_MACHINE_NAME); + join_ctx2 = torture_create_testuser(torture, test_wksta_machine_account, lpcfg_workgroup(torture->lp_ctx), ACB_WSTRUST, &wksta_machine_password); + if (!join_ctx2) { + talloc_free(mem_ctx); + torture_comment(torture, "Failed to join as member\n"); + return false; + } + + user_ctx = torture_create_testuser(torture, TEST_USER_NAME, + lpcfg_workgroup(torture->lp_ctx), + ACB_NORMAL, NULL); + if (!user_ctx) { + talloc_free(mem_ctx); + torture_comment(torture, "Failed to create test account\n"); + return false; + } + + samsync_state = talloc_zero(mem_ctx, struct samsync_state); + + samsync_state->p_samr = torture_join_samr_pipe(join_ctx); + samsync_state->b_samr = samsync_state->p_samr->binding_handle; + samsync_state->connect_handle = talloc_zero(samsync_state, struct policy_handle); + samsync_state->lsa_handle = talloc_zero(samsync_state, struct policy_handle); + c.in.system_name = NULL; + c.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + c.out.connect_handle = samsync_state->connect_handle; + + torture_assert_ntstatus_ok_goto(torture, + dcerpc_samr_Connect_r(samsync_state->b_samr, mem_ctx, &c), + ret, failed, + "samr_Connect failed"); + torture_assert_ntstatus_ok_goto(torture, c.out.result, + ret, failed, + "samr_Connect failed"); + + domain_policy = samsync_open_domain(torture, mem_ctx, samsync_state, lpcfg_workgroup(torture->lp_ctx), NULL); + if (!domain_policy) { + torture_comment(torture, "samrsync_open_domain failed\n"); + ret = false; + goto failed; + } + + s.in.domain_handle = domain_policy; + s.in.level = 4; + s.in.info = talloc(mem_ctx, union samr_DomainInfo); + + s.in.info->oem.oem_information.string + = talloc_asprintf(mem_ctx, + "Tortured by Samba4: %s", + timestring(mem_ctx, time(NULL))); + torture_assert_ntstatus_ok_goto(torture, + dcerpc_samr_SetDomainInfo_r(samsync_state->b_samr, mem_ctx, &s), + ret, failed, + "SetDomainInfo failed"); + + if (!test_samr_handle_Close(samsync_state->b_samr, torture, domain_policy)) { + ret = false; + goto failed; + } + + torture_assert_ntstatus_ok_goto(torture, s.out.result, + ret, failed, + talloc_asprintf(torture, "SetDomainInfo level %u failed", s.in.level)); + + status = torture_rpc_connection(torture, + &samsync_state->p_lsa, + &ndr_table_lsarpc); + + if (!NT_STATUS_IS_OK(status)) { + ret = false; + goto failed; + } + samsync_state->b_lsa = samsync_state->p_lsa->binding_handle; + + qos.len = 0; + qos.impersonation_level = 2; + qos.context_mode = 1; + qos.effective_only = 0; + + attr.len = 0; + attr.root_dir = NULL; + attr.object_name = NULL; + attr.attributes = 0; + attr.sec_desc = NULL; + attr.sec_qos = &qos; + + r.in.system_name = "\\"; + r.in.attr = &attr; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = samsync_state->lsa_handle; + + torture_assert_ntstatus_ok_goto(torture, + dcerpc_lsa_OpenPolicy2_r(samsync_state->b_lsa, mem_ctx, &r), + ret, failed, + "OpenPolicy2 failed"); + torture_assert_ntstatus_ok_goto(torture, r.out.result, + ret, failed, + "OpenPolicy2 failed"); + + status = torture_rpc_binding(torture, &b); + if (!NT_STATUS_IS_OK(status)) { + ret = false; + goto failed; + } + + status = dcerpc_binding_set_flags(b, + DCERPC_SCHANNEL | DCERPC_SIGN, + DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(torture, status, "set flags"); + + credentials = cli_credentials_init(mem_ctx); + + cli_credentials_set_workstation(credentials, TEST_MACHINE_NAME, CRED_SPECIFIED); + cli_credentials_set_domain(credentials, lpcfg_workgroup(torture->lp_ctx), CRED_SPECIFIED); + cli_credentials_set_username(credentials, test_machine_account, CRED_SPECIFIED); + cli_credentials_set_password(credentials, machine_password, CRED_SPECIFIED); + cli_credentials_set_secure_channel_type(credentials, + SEC_CHAN_BDC); + + status = dcerpc_pipe_connect_b(samsync_state, + &samsync_state->p, b, + &ndr_table_netlogon, + credentials, torture->ev, torture->lp_ctx); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "Failed to connect to server as a BDC: %s\n", nt_errstr(status)); + ret = false; + goto failed; + } + samsync_state->b = samsync_state->p->binding_handle; + + samsync_state->creds = cli_credentials_get_netlogon_creds(credentials); + if (samsync_state->creds == NULL) { + ret = false; + } + + + + status = torture_rpc_binding(torture, &b_netlogon_wksta); + if (!NT_STATUS_IS_OK(status)) { + ret = false; + goto failed; + } + + status = dcerpc_binding_set_flags(b_netlogon_wksta, + DCERPC_SCHANNEL | DCERPC_SIGN, + DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(torture, status, "set flags"); + + credentials_wksta = cli_credentials_init(mem_ctx); + + cli_credentials_set_workstation(credentials_wksta, TEST_WKSTA_MACHINE_NAME, CRED_SPECIFIED); + cli_credentials_set_domain(credentials_wksta, lpcfg_workgroup(torture->lp_ctx), CRED_SPECIFIED); + cli_credentials_set_username(credentials_wksta, test_wksta_machine_account, CRED_SPECIFIED); + cli_credentials_set_password(credentials_wksta, wksta_machine_password, CRED_SPECIFIED); + cli_credentials_set_secure_channel_type(credentials_wksta, + SEC_CHAN_WKSTA); + + status = dcerpc_pipe_connect_b(samsync_state, + &samsync_state->p_netlogon_wksta, + b_netlogon_wksta, + &ndr_table_netlogon, + credentials_wksta, torture->ev, torture->lp_ctx); + + if (!NT_STATUS_IS_OK(status)) { + torture_comment(torture, "Failed to connect to server as a Workstation: %s\n", nt_errstr(status)); + ret = false; + goto failed; + } + + samsync_state->creds_netlogon_wksta = cli_credentials_get_netlogon_creds(credentials_wksta); + if (samsync_state->creds_netlogon_wksta == NULL) { + torture_comment(torture, "Failed to obtail schanel creds!\n"); + ret = false; + } + + if (!test_DatabaseSync(torture, samsync_state, mem_ctx)) { + torture_comment(torture, "DatabaseSync failed\n"); + ret = false; + } + + if (!test_DatabaseDeltas(torture, samsync_state, mem_ctx)) { + torture_comment(torture, "DatabaseDeltas failed\n"); + ret = false; + } + + if (!test_DatabaseSync2(torture, samsync_state->p, mem_ctx, samsync_state->creds)) { + torture_comment(torture, "DatabaseSync2 failed\n"); + ret = false; + } +failed: + + torture_leave_domain(torture, join_ctx); + torture_leave_domain(torture, join_ctx2); + torture_leave_domain(torture, user_ctx); + + talloc_free(mem_ctx); + + return ret; +} diff --git a/source4/torture/rpc/scanner.c b/source4/torture/rpc/scanner.c new file mode 100644 index 0000000..261c3b9 --- /dev/null +++ b/source4/torture/rpc/scanner.c @@ -0,0 +1,187 @@ +/* + Unix SMB/CIFS implementation. + + scanner for rpc calls + + Copyright (C) Andrew Tridgell 2003 + + 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_mgmt_c.h" +#include "librpc/ndr/ndr_table.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + +/* + work out how many calls there are for an interface + */ +static bool test_num_calls(struct torture_context *tctx, + const struct ndr_interface_table *iface, + TALLOC_CTX *mem_ctx, + struct ndr_syntax_id *id) +{ + struct dcerpc_pipe *p; + NTSTATUS status; + unsigned int i; + DATA_BLOB stub_in, stub_out; + struct ndr_interface_table _tbl; + const struct ndr_interface_table *tbl; + + /* FIXME: This should be fixed when torture_rpc_connection + * takes a ndr_syntax_id */ + tbl = ndr_table_by_syntax(id); + if (tbl == NULL) { + _tbl = *iface; + _tbl.name = "__unknown__"; + _tbl.syntax_id = *id; + _tbl.num_calls = UINT32_MAX; + tbl = &_tbl; + } + + status = torture_rpc_connection(tctx, &p, tbl); + if (!NT_STATUS_IS_OK(status)) { + char *uuid_str = GUID_string(mem_ctx, &id->uuid); + printf("Failed to connect to '%s' on '%s' - %s\n", + uuid_str, iface->name, nt_errstr(status)); + talloc_free(uuid_str); + return true; + } + + /* make null calls */ + stub_in = data_blob_talloc(mem_ctx, NULL, 1000); + memset(stub_in.data, 0xFF, stub_in.length); + + for (i=0;i<200;i++) { + bool ok; + uint32_t out_flags = 0; + + status = dcerpc_binding_handle_raw_call(p->binding_handle, + NULL, i, + 0, /* in_flags */ + stub_in.data, + stub_in.length, + mem_ctx, + &stub_out.data, + &stub_out.length, + &out_flags); + ok = dcerpc_binding_handle_is_connected(p->binding_handle); + if (!ok) { + printf("\tpipe disconnected at %u\n", i); + goto done; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + break; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + printf("\taccess denied at %u\n", i); + goto done; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) { + printf("\tprotocol error at %u\n", i); + } + } + + printf("\t%d calls available\n", i); + if (tbl->num_calls == UINT32_MAX) { + printf("\tinterface not known in local IDL\n"); + } else if (tbl->num_calls != i) { + printf("\tWARNING: local IDL defines %u calls\n", + (unsigned int)tbl->num_calls); + } else { + printf("\tOK: matches num_calls in local IDL\n"); + } + +done: + talloc_free(p); + return true; +} + + + +bool torture_rpc_scanner(struct torture_context *torture) +{ + NTSTATUS status; + struct dcerpc_pipe *p; + TALLOC_CTX *loop_ctx; + bool ret = true; + const struct ndr_interface_list *l; + struct dcerpc_binding *b; + enum dcerpc_transport_t transport; + + status = torture_rpc_binding(torture, &b); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + transport = dcerpc_binding_get_transport(b); + + for (l=ndr_table_list();l;l=l->next) { + loop_ctx = talloc_named(torture, 0, "torture_rpc_scanner loop context"); + /* some interfaces are not mappable */ + if (l->table->num_calls == 0 || + strcmp(l->table->name, "mgmt") == 0) { + talloc_free(loop_ctx); + continue; + } + + printf("\nTesting pipe '%s'\n", l->table->name); + + if (transport == NCACN_IP_TCP) { + status = dcerpc_epm_map_binding(torture, b, l->table, + torture->ev, + torture->lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to map port for uuid %s\n", + GUID_string(loop_ctx, &l->table->syntax_id.uuid)); + talloc_free(loop_ctx); + continue; + } + } else { + status = dcerpc_binding_set_string_option(b, "endpoint", + l->table->name); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(loop_ctx); + ret = false; + continue; + } + status = dcerpc_binding_set_abstract_syntax(b, + &l->table->syntax_id); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(loop_ctx); + ret = false; + continue; + } + } + + lpcfg_set_cmdline(torture->lp_ctx, "torture:binding", dcerpc_binding_string(torture, b)); + + status = torture_rpc_connection(torture, &p, &ndr_table_mgmt); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(loop_ctx); + ret = false; + continue; + } + + if (!test_inq_if_ids(torture, p->binding_handle, torture, test_num_calls, l->table)) { + ret = false; + } + } + + return ret; +} + diff --git a/source4/torture/rpc/schannel.c b/source4/torture/rpc/schannel.c new file mode 100644 index 0000000..ed94c91 --- /dev/null +++ b/source4/torture/rpc/schannel.c @@ -0,0 +1,1338 @@ +/* + Unix SMB/CIFS implementation. + + test suite for schannel operations + + 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 "includes.h" +#include "librpc/gen_ndr/ndr_netlogon_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_krb5.h" +#include "torture/rpc/torture_rpc.h" +#include "lib/cmdline/cmdline.h" +#include "../libcli/auth/schannel.h" +#include "libcli/auth/libcli_auth.h" +#include "libcli/security/security.h" +#include "system/filesys.h" +#include "param/param.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "libcli/composite/composite.h" +#include "lib/events/events.h" + +#define TEST_MACHINE_NAME "schannel" + +/* + try a netlogon SamLogon +*/ +bool test_netlogon_ex_ops(struct dcerpc_pipe *p, struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + struct netr_LogonSamLogonEx r; + struct netr_NetworkInfo ninfo; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative = 1; + uint32_t _flags = 0; + DATA_BLOB names_blob, chal, lm_resp, nt_resp; + int i; + int flags = CLI_CRED_NTLM_AUTH; + struct dcerpc_binding_handle *b = p->binding_handle; + + struct netr_UserSessionKey key; + struct netr_LMSessionKey LMSessKey; + uint32_t validation_levels[] = { 2, 3 }; + struct netr_SamBaseInfo *base = NULL; + const char *crypto_alg = ""; + bool can_do_validation_6 = true; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + if (lpcfg_client_lanman_auth(tctx->lp_ctx)) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (lpcfg_client_ntlmv2_auth(tctx->lp_ctx)) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + + cli_credentials_get_ntlm_username_domain(samba_cmdline_get_creds(), + tctx, + &ninfo.identity_info.account_name.string, + &ninfo.identity_info.domain_name.string); + + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + chal = data_blob_const(ninfo.challenge, + sizeof(ninfo.challenge)); + + names_blob = NTLMv2_generate_names_blob(tctx, cli_credentials_get_workstation(credentials), + cli_credentials_get_domain(credentials)); + + status = cli_credentials_get_ntlm_response( + samba_cmdline_get_creds(), + tctx, + &flags, + chal, + NULL, /* server_timestamp */ + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(tctx, status, + "cli_credentials_get_ntlm_response failed"); + + ninfo.lm.data = lm_resp.data; + ninfo.lm.length = lm_resp.length; + + ninfo.nt.data = nt_resp.data; + ninfo.nt.length = nt_resp.length; + + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.workstation.string = cli_credentials_get_workstation(credentials); + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(credentials); + r.in.logon_level = NetlogonNetworkInformation; + r.in.logon= &logon; + r.in.flags = &_flags; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + r.out.flags = &_flags; + + /* + - retrieve level6 + - save usrsession and lmsession key + - retrieve level 2 + - calculate, compare + - retrieve level 3 + - calculate, compare + */ + + if (creds) { + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + crypto_alg = "AES"; + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + crypto_alg = "ARCFOUR"; + } + } + + dcerpc_binding_handle_auth_info(b, NULL, &auth_level); + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + r.in.validation_level = 6; + + torture_comment(tctx, + "Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "LogonSamLogonEx failed"); + } else { + torture_comment(tctx, + "Skip auth_level[%u] Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + auth_level, ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + r.out.result = NT_STATUS_INVALID_INFO_CLASS; + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_INFO_CLASS)) { + can_do_validation_6 = false; + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, + "LogonSamLogonEx failed"); + + key = r.out.validation->sam6->base.key; + LMSessKey = r.out.validation->sam6->base.LMSessKey; + + DEBUG(1,("unencrypted session keys from validation_level 6:\n")); + dump_data(1, r.out.validation->sam6->base.key.key, 16); + dump_data(1, r.out.validation->sam6->base.LMSessKey.key, 8); + } + + for (i=0; i < ARRAY_SIZE(validation_levels); i++) { + + r.in.validation_level = validation_levels[i]; + + torture_comment(tctx, + "Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "LogonSamLogonEx failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "LogonSamLogonEx failed"); + + if (creds == NULL) { + /* when this test is called without creds no point in + * testing the session keys */ + continue; + } + + switch (validation_levels[i]) { + case 2: + base = &r.out.validation->sam2->base; + break; + case 3: + base = &r.out.validation->sam3->base; + break; + default: + break; + } + + DEBUG(1,("encrypted keys validation_level %d:\n", + validation_levels[i])); + dump_data(1, base->key.key, 16); + dump_data(1, base->LMSessKey.key, 8); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_decrypt(creds, base->key.key, 16); + netlogon_creds_aes_decrypt(creds, base->LMSessKey.key, 8); + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + netlogon_creds_arcfour_crypt(creds, base->key.key, 16); + netlogon_creds_arcfour_crypt(creds, base->LMSessKey.key, 8); + } + + DEBUG(1,("decryped keys validation_level %d\n", + validation_levels[i])); + + dump_data(1, base->key.key, 16); + dump_data(1, base->LMSessKey.key, 8); + + if (!can_do_validation_6) { + /* we cant compare against unencrypted keys */ + continue; + } + + torture_assert_mem_equal(tctx, + base->key.key, + key.key, + 16, + "unexpected user session key\n"); + torture_assert_mem_equal(tctx, + base->LMSessKey.key, + LMSessKey.key, + 8, + "unexpected LM session key\n"); + } + + return true; +} + +static bool test_netlogon_ex_bug14932(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct cli_credentials *credentials, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + struct netr_LogonSamLogonEx r; + struct netr_NetworkInfo ninfo; + union netr_LogonLevel logon; + union netr_Validation validation; + uint8_t authoritative = 1; + uint32_t _flags = 0; + static const char *netapp_magic = + "\x01\x01\x00\x00\x00\x00\x00\x00" + "\x3f\x3f\x3f\x3f\x3f\x3f\x3f\x3f" + "\xb8\x82\x3a\xf1\xb3\xdd\x08\x15" + "\x00\x00\x00\x00\x11\xa2\x08\x81" + "\x50\x38\x22\x78\x2b\x94\x47\xfe" + "\x54\x94\x7b\xff\x17\x27\x5a\xb4" + "\xf4\x18\xba\xdc\x2c\x38\xfd\x5b" + "\xfb\x0e\xc1\x85\x1e\xcc\x92\xbb" + "\x9b\xb1\xc4\xd5\x53\x14\xff\x8c" + "\x76\x49\xf5\x45\x90\x19\xa2"; + NTTIME timestamp = BVAL(netapp_magic, 8); + DATA_BLOB names_blob = data_blob_string_const(netapp_magic + 28); + DATA_BLOB chal, lm_resp, nt_resp; + int i; + int flags = CLI_CRED_NTLM_AUTH; + struct dcerpc_binding_handle *b = p->binding_handle; + struct netr_UserSessionKey key; + struct netr_LMSessionKey LMSessKey; + uint32_t validation_levels[] = { 2, 3 }; + struct netr_SamBaseInfo *base = NULL; + const char *crypto_alg = ""; + bool can_do_validation_6 = true; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + flags |= CLI_CRED_NTLMv2_AUTH; + + cli_credentials_get_ntlm_username_domain(samba_cmdline_get_creds(), + tctx, + &ninfo.identity_info.account_name.string, + &ninfo.identity_info.domain_name.string); + + generate_random_buffer(ninfo.challenge, + sizeof(ninfo.challenge)); + + chal = data_blob_const(ninfo.challenge, + sizeof(ninfo.challenge)); + + status = cli_credentials_get_ntlm_response( + samba_cmdline_get_creds(), + tctx, + &flags, + chal, + ×tamp, + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(tctx, status, + "cli_credentials_get_ntlm_response failed"); + + ninfo.lm.data = lm_resp.data; + ninfo.lm.length = lm_resp.length; + + ninfo.nt.data = nt_resp.data; + ninfo.nt.length = nt_resp.length; + + ninfo.identity_info.parameter_control = 0; + ninfo.identity_info.logon_id = 0; + ninfo.identity_info.workstation.string = cli_credentials_get_workstation(credentials); + + logon.network = &ninfo; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.computer_name = cli_credentials_get_workstation(credentials); + r.in.logon_level = NetlogonNetworkInformation; + r.in.logon= &logon; + r.in.flags = &_flags; + r.out.validation = &validation; + r.out.authoritative = &authoritative; + r.out.flags = &_flags; + + /* + - retrieve level6 + - save usrsession and lmsession key + - retrieve level 2 + - calculate, compare + - retrieve level 3 + - calculate, compare + */ + + if (creds != NULL) { + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + crypto_alg = "AES"; + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + crypto_alg = "ARCFOUR"; + } + } + + dcerpc_binding_handle_auth_info(b, NULL, &auth_level); + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + r.in.validation_level = 6; + + torture_comment(tctx, + "Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "LogonSamLogonEx failed"); + } else { + torture_comment(tctx, + "Skip auth_level[%u] Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + auth_level, ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + r.out.result = NT_STATUS_INVALID_INFO_CLASS; + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_INVALID_INFO_CLASS)) { + can_do_validation_6 = false; + } else { + torture_assert_ntstatus_ok(tctx, r.out.result, + "LogonSamLogonEx failed"); + + key = r.out.validation->sam6->base.key; + LMSessKey = r.out.validation->sam6->base.LMSessKey; + + DEBUG(1,("unencrypted session keys from validation_level 6:\n")); + dump_data(1, r.out.validation->sam6->base.key.key, 16); + dump_data(1, r.out.validation->sam6->base.LMSessKey.key, 8); + } + + for (i=0; i < ARRAY_SIZE(validation_levels); i++) { + + r.in.validation_level = validation_levels[i]; + + torture_comment(tctx, + "Testing LogonSamLogonEx with name %s using %s and validation_level: %d\n", + ninfo.identity_info.account_name.string, crypto_alg, + r.in.validation_level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_netr_LogonSamLogonEx_r(b, tctx, &r), + "LogonSamLogonEx failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "LogonSamLogonEx failed"); + + if (creds == NULL) { + /* when this test is called without creds no point in + * testing the session keys */ + continue; + } + + switch (validation_levels[i]) { + case 2: + base = &r.out.validation->sam2->base; + break; + case 3: + base = &r.out.validation->sam3->base; + break; + default: + break; + } + + DEBUG(1,("encrypted keys validation_level %d:\n", + validation_levels[i])); + dump_data(1, base->key.key, 16); + dump_data(1, base->LMSessKey.key, 8); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + netlogon_creds_aes_decrypt(creds, base->key.key, 16); + netlogon_creds_aes_decrypt(creds, base->LMSessKey.key, 8); + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + netlogon_creds_arcfour_crypt(creds, base->key.key, 16); + netlogon_creds_arcfour_crypt(creds, base->LMSessKey.key, 8); + } + + DEBUG(1,("decryped keys validation_level %d\n", + validation_levels[i])); + + dump_data(1, base->key.key, 16); + dump_data(1, base->LMSessKey.key, 8); + + if (!can_do_validation_6) { + /* we cant compare against unencrypted keys */ + continue; + } + + torture_assert_mem_equal(tctx, + base->key.key, + key.key, + 16, + "unexpected user session key\n"); + torture_assert_mem_equal(tctx, + base->LMSessKey.key, + LMSessKey.key, + 8, + "unexpected LM session key\n"); + } + + return true; +} + +/* + do some samr ops using the schannel connection + */ +static bool test_samr_ops(struct torture_context *tctx, + struct dcerpc_binding_handle *b) +{ + struct samr_GetDomPwInfo r; + struct samr_PwInfo info; + struct samr_Connect connect_r; + struct samr_OpenDomain opendom; + int i; + struct lsa_String name; + struct policy_handle handle; + struct policy_handle domain_handle; + + name.string = lpcfg_workgroup(tctx->lp_ctx); + r.in.domain_name = &name; + r.out.info = &info; + + connect_r.in.system_name = 0; + connect_r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + connect_r.out.connect_handle = &handle; + + torture_comment(tctx, "Testing Connect and OpenDomain on BUILTIN\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_Connect_r(b, tctx, &connect_r), + "Connect failed"); + if (!NT_STATUS_IS_OK(connect_r.out.result)) { + if (NT_STATUS_EQUAL(connect_r.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, "Connect failed (expected, schannel mapped to anonymous): %s\n", + nt_errstr(connect_r.out.result)); + } else { + torture_comment(tctx, "Connect failed - %s\n", nt_errstr(connect_r.out.result)); + return false; + } + } else { + opendom.in.connect_handle = &handle; + opendom.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + opendom.in.sid = dom_sid_parse_talloc(tctx, "S-1-5-32"); + opendom.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_samr_OpenDomain_r(b, tctx, &opendom), + "OpenDomain failed"); + if (!NT_STATUS_IS_OK(opendom.out.result)) { + torture_comment(tctx, "OpenDomain failed - %s\n", nt_errstr(opendom.out.result)); + return false; + } + } + + torture_comment(tctx, "Testing GetDomPwInfo with name %s\n", r.in.domain_name->string); + + /* do several ops to test credential chaining */ + for (i=0;i<5;i++) { + torture_assert_ntstatus_ok(tctx, dcerpc_samr_GetDomPwInfo_r(b, tctx, &r), + "GetDomPwInfo failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + if (!NT_STATUS_EQUAL(r.out.result, NT_STATUS_ACCESS_DENIED)) { + torture_comment(tctx, "GetDomPwInfo op %d failed - %s\n", i, nt_errstr(r.out.result)); + return false; + } + } + } + + return true; +} + + +/* + do some lsa ops using the schannel connection + */ +static bool test_lsa_ops(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct lsa_GetUserName r; + bool ret = true; + struct lsa_String *account_name_p = NULL; + struct lsa_String *authority_name_p = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "\nTesting GetUserName\n"); + + r.in.system_name = "\\"; + r.in.account_name = &account_name_p; + r.in.authority_name = &authority_name_p; + r.out.account_name = &account_name_p; + + /* do several ops to test credential chaining and various operations */ + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_GetUserName_r(b, tctx, &r), + "lsa_GetUserName failed"); + + authority_name_p = *r.out.authority_name; + + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "GetUserName failed - %s\n", nt_errstr(r.out.result)); + return false; + } else { + if (!r.out.account_name) { + return false; + } + + if (strcmp(account_name_p->string, "ANONYMOUS LOGON") != 0) { + torture_comment(tctx, "GetUserName returned wrong user: %s, expected %s\n", + account_name_p->string, "ANONYMOUS LOGON"); + /* FIXME: gd */ + if (!torture_setting_bool(tctx, "samba3", false)) { + return false; + } + } + if (!authority_name_p || !authority_name_p->string) { + return false; + } + + if (strcmp(authority_name_p->string, "NT AUTHORITY") != 0) { + torture_comment(tctx, "GetUserName returned wrong user: %s, expected %s\n", + authority_name_p->string, "NT AUTHORITY"); + /* FIXME: gd */ + if (!torture_setting_bool(tctx, "samba3", false)) { + return false; + } + } + } + + return ret; +} + + +/* + test a schannel connection with the given flags + */ +static bool test_schannel(struct torture_context *tctx, + uint16_t acct_flags, uint32_t dcerpc_flags, + int i) +{ + struct test_join *join_ctx; + NTSTATUS status; + const char *binding = torture_setting_string(tctx, "binding", NULL); + struct dcerpc_binding *b; + struct dcerpc_pipe *p = NULL; + struct dcerpc_pipe *p_netlogon = NULL; + struct dcerpc_pipe *p_netlogon2 = NULL; + struct dcerpc_pipe *p_netlogon3 = NULL; + struct dcerpc_pipe *p_samr2 = NULL; + struct dcerpc_pipe *p_lsa = NULL; + struct netlogon_creds_CredentialState *creds; + struct cli_credentials *credentials; + enum dcerpc_transport_t transport; + + join_ctx = torture_join_domain(tctx, + talloc_asprintf(tctx, "%s%d", TEST_MACHINE_NAME, i), + acct_flags, &credentials); + torture_assert(tctx, join_ctx != NULL, "Failed to join domain"); + + status = dcerpc_parse_binding(tctx, binding, &b); + torture_assert_ntstatus_ok(tctx, status, "Bad binding string"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_pipe_connect_b(tctx, &p, b, &ndr_table_samr, + credentials, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, + "Failed to connect to samr with schannel"); + + torture_assert(tctx, test_samr_ops(tctx, p->binding_handle), + "Failed to process schannel secured SAMR ops"); + + /* Also test that when we connect to the netlogon pipe, that + * the credentials we setup on the first pipe are valid for + * the second */ + + /* Swap the binding details from SAMR to NETLOGON */ + status = dcerpc_epm_map_binding(tctx, b, &ndr_table_netlogon, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "epm map"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_secondary_auth_connection(p, b, &ndr_table_netlogon, + credentials, tctx->lp_ctx, + tctx, &p_netlogon); + torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection"); + + creds = cli_credentials_get_netlogon_creds(credentials); + torture_assert(tctx, (creds != NULL), "schannel creds"); + + /* checks the capabilities */ + torture_assert(tctx, test_netlogon_capabilities(p_netlogon, tctx, credentials, creds), + "Failed to process schannel secured capability ops (on fresh connection)"); + + /* do a couple of logins */ + torture_assert(tctx, test_netlogon_ops(p_netlogon, tctx, credentials, creds), + "Failed to process schannel secured NETLOGON ops"); + + torture_assert(tctx, test_netlogon_ex_ops(p_netlogon, tctx, credentials, creds), + "Failed to process schannel secured NETLOGON EX ops"); + + /* regression test for https://bugzilla.samba.org/show_bug.cgi?id=14932 */ + torture_assert(tctx, test_netlogon_ex_bug14932(p_netlogon, tctx, credentials, creds), + "Failed to process schannel secured NETLOGON EX for BUG 14932"); + + /* we *MUST* use ncacn_np for openpolicy etc. */ + transport = dcerpc_binding_get_transport(b); + status = dcerpc_binding_set_transport(b, NCACN_NP); + torture_assert_ntstatus_ok(tctx, status, "set transport"); + + /* Swap the binding details from SAMR to LSARPC */ + status = dcerpc_epm_map_binding(tctx, b, &ndr_table_lsarpc, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "epm map"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p_lsa, b, &ndr_table_lsarpc, + credentials, tctx->ev, tctx->lp_ctx), + "failed to connect lsarpc with schannel"); + + torture_assert(tctx, test_lsa_ops(tctx, p_lsa), + "Failed to process schannel secured LSA ops"); + + talloc_free(p_lsa); + p_lsa = NULL; + + /* we *MUST* use ncacn_ip_tcp for lookupsids3/lookupnames4 */ + status = dcerpc_binding_set_transport(b, NCACN_IP_TCP); + torture_assert_ntstatus_ok(tctx, status, "set transport"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_epm_map_binding(tctx, b, &ndr_table_lsarpc, tctx->ev, tctx->lp_ctx), + "failed to call epm map"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &p_lsa, b, &ndr_table_lsarpc, + credentials, tctx->ev, tctx->lp_ctx), + "failed to connect lsarpc with schannel"); + + torture_assert(tctx, + test_many_LookupSids(p_lsa, tctx, NULL, LSA_LOOKUP_NAMES_ALL), + "LsaLookupSids3 failed!\n"); + + status = dcerpc_binding_set_transport(b, transport); + torture_assert_ntstatus_ok(tctx, status, "set transport"); + + + /* Drop the socket, we want to start from scratch */ + talloc_free(p); + p = NULL; + + /* Now see what we are still allowed to do */ + + status = dcerpc_parse_binding(tctx, binding, &b); + torture_assert_ntstatus_ok(tctx, status, "Bad binding string"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_pipe_connect_b(tctx, &p_samr2, b, &ndr_table_samr, + credentials, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, + "Failed to connect with schannel"); + + /* do a some SAMR operations. We have *not* done a new serverauthenticate */ + torture_assert (tctx, test_samr_ops(tctx, p_samr2->binding_handle), + "Failed to process schannel secured SAMR ops (on fresh connection)"); + + /* Swap the binding details from SAMR to NETLOGON */ + status = dcerpc_epm_map_binding(tctx, b, &ndr_table_netlogon, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "epm"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_secondary_auth_connection(p_samr2, b, &ndr_table_netlogon, + credentials, tctx->lp_ctx, + tctx, &p_netlogon2); + torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection"); + + /* checks the capabilities */ + torture_assert(tctx, test_netlogon_capabilities(p_netlogon2, tctx, credentials, creds), + "Failed to process schannel secured capability ops (on fresh connection)"); + + /* Try the schannel-only SamLogonEx operation */ + torture_assert(tctx, test_netlogon_ex_ops(p_netlogon2, tctx, credentials, creds), + "Failed to process schannel secured NETLOGON EX ops (on fresh connection)"); + + + /* And the more traditional style, proving that the + * credentials chaining state is fully present */ + torture_assert(tctx, test_netlogon_ops(p_netlogon2, tctx, credentials, creds), + "Failed to process schannel secured NETLOGON ops (on fresh connection)"); + + /* Drop the socket, we want to start from scratch (again) */ + talloc_free(p_samr2); + + /* We don't want schannel for this test */ + status = dcerpc_binding_set_flags(b, 0, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_pipe_connect_b(tctx, &p_netlogon3, b, &ndr_table_netlogon, + credentials, tctx->ev, tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "Failed to connect without schannel"); + + torture_assert(tctx, !test_netlogon_ex_ops(p_netlogon3, tctx, credentials, creds), + "Processed NOT schannel secured NETLOGON EX ops without SCHANNEL (unsafe)"); + + /* Required because the previous call will mark the current context as having failed */ + tctx->last_result = TORTURE_OK; + tctx->last_reason = NULL; + + torture_assert(tctx, test_netlogon_ops(p_netlogon3, tctx, credentials, creds), + "Failed to processed NOT schannel secured NETLOGON ops without new ServerAuth"); + + torture_leave_domain(tctx, join_ctx); + return true; +} + +/* + * Purpose of this test is to demonstrate that a netlogon server carefully deals + * with anonymous attempts to set passwords, in particular when the server + * enforces the use of schannel. This test makes most sense to be run in an + * environment where the netlogon server enforces use of schannel. + */ + +static bool test_schannel_anonymous_setPassword(struct torture_context *tctx, + uint32_t dcerpc_flags, + bool use2) +{ + NTSTATUS status, result; + const char *binding = torture_setting_string(tctx, "binding", NULL); + struct dcerpc_binding *b; + struct dcerpc_pipe *p = NULL; + struct cli_credentials *credentials; + bool ok = true; + + credentials = cli_credentials_init(NULL); + torture_assert(tctx, credentials != NULL, "Bad credentials"); + cli_credentials_set_anonymous(credentials); + + status = dcerpc_parse_binding(tctx, binding, &b); + torture_assert_ntstatus_ok(tctx, status, "Bad binding string"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(tctx, status, "set flags"); + + status = dcerpc_pipe_connect_b(tctx, + &p, + b, + &ndr_table_netlogon, + credentials, + tctx->ev, + tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, "Failed to connect without schannel"); + + if (use2) { + struct netr_ServerPasswordSet2 r = {}; + struct netr_Authenticator credential = {}; + struct netr_Authenticator return_authenticator = {}; + struct netr_CryptPassword new_password = {}; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = 0; + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + status = dcerpc_netr_ServerPasswordSet2_r(p->binding_handle, tctx, &r); + result = r.out.result; + } else { + struct netr_ServerPasswordSet r = {}; + struct netr_Authenticator credential = {}; + struct netr_Authenticator return_authenticator = {}; + struct samr_Password new_password = {}; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.account_name = talloc_asprintf(tctx, "%s$", TEST_MACHINE_NAME); + r.in.secure_channel_type = 0; + r.in.computer_name = TEST_MACHINE_NAME; + r.in.credential = &credential; + r.in.new_password = &new_password; + r.out.return_authenticator = &return_authenticator; + + status = dcerpc_netr_ServerPasswordSet_r(p->binding_handle, tctx, &r); + result = r.out.result; + } + + torture_assert_ntstatus_ok(tctx, status, "ServerPasswordSet failed"); + + if (NT_STATUS_IS_OK(result)) { + torture_fail(tctx, "unexpectedly received NT_STATUS_OK"); + } + + return ok; +} + + +/* + a schannel test suite + */ +bool torture_rpc_schannel(struct torture_context *torture) +{ + bool ret = true; + struct { + uint16_t acct_flags; + uint32_t dcerpc_flags; + } tests[] = { + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_AUTO}, + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_AUTO}, + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_128}, + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_128 }, + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_AES}, + { ACB_WSTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_AES }, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_AUTO}, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_AUTO}, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_128 }, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_128 }, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_AES }, + { ACB_SVRTRUST, DCERPC_SCHANNEL | DCERPC_SEAL | DCERPC_SCHANNEL_AES } + }; + int i; + + for (i=0;i<ARRAY_SIZE(tests);i++) { + torture_comment(torture, "Testing with acct_flags=0x%x dcerpc_flags=0x%x \n", + tests[i].acct_flags, tests[i].dcerpc_flags); + + if (!test_schannel(torture, + tests[i].acct_flags, tests[i].dcerpc_flags, + i)) { + torture_comment(torture, "Failed with acct_flags=0x%x dcerpc_flags=0x%x \n", + tests[i].acct_flags, tests[i].dcerpc_flags); + ret = false; + } + } + + return ret; +} + +bool torture_rpc_schannel_anon_setpw(struct torture_context *torture) +{ + bool ret = true; + bool ok; + uint32_t dcerpc_flags = DCERPC_SCHANNEL | DCERPC_SIGN | DCERPC_SCHANNEL_AUTO; + + ok = test_schannel_anonymous_setPassword(torture, + dcerpc_flags, + true); + if (!ok) { + torture_comment(torture, + "Failed with dcerpc_flags=0x%x\n", + dcerpc_flags); + ret = false; + } + + ok = test_schannel_anonymous_setPassword(torture, + dcerpc_flags, + false); + if (!ok) { + torture_comment(torture, + "Failed with dcerpc_flags=0x%x\n", + dcerpc_flags); + ret = false; + } + + return ret; +} + +/* + test two schannel connections + */ +bool torture_rpc_schannel2(struct torture_context *torture) +{ + struct test_join *join_ctx; + NTSTATUS status; + const char *binding = torture_setting_string(torture, "binding", NULL); + struct dcerpc_binding *b; + struct dcerpc_pipe *p1 = NULL, *p2 = NULL; + struct cli_credentials *credentials1, *credentials2; + uint32_t dcerpc_flags = DCERPC_SCHANNEL | DCERPC_SCHANNEL_AUTO | DCERPC_SIGN; + + join_ctx = torture_join_domain(torture, talloc_asprintf(torture, "%s2", TEST_MACHINE_NAME), + ACB_WSTRUST, &credentials1); + torture_assert(torture, join_ctx != NULL, + "Failed to join domain with acct_flags=ACB_WSTRUST"); + + credentials2 = cli_credentials_shallow_copy(torture, credentials1); + cli_credentials_set_netlogon_creds(credentials1, NULL); + cli_credentials_set_netlogon_creds(credentials2, NULL); + + status = dcerpc_parse_binding(torture, binding, &b); + torture_assert_ntstatus_ok(torture, status, "Bad binding string"); + + status = dcerpc_binding_set_flags(b, dcerpc_flags, DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(torture, status, "set flags"); + + torture_comment(torture, "Opening first connection\n"); + status = dcerpc_pipe_connect_b(torture, &p1, b, &ndr_table_netlogon, + credentials1, torture->ev, torture->lp_ctx); + torture_assert_ntstatus_ok(torture, status, "Failed to connect with schannel"); + + torture_comment(torture, "Opening second connection\n"); + status = dcerpc_pipe_connect_b(torture, &p2, b, &ndr_table_netlogon, + credentials2, torture->ev, torture->lp_ctx); + torture_assert_ntstatus_ok(torture, status, "Failed to connect with schannel"); + + cli_credentials_set_netlogon_creds(credentials1, NULL); + cli_credentials_set_netlogon_creds(credentials2, NULL); + + torture_comment(torture, "Testing logon on pipe1\n"); + if (!test_netlogon_ex_ops(p1, torture, credentials1, NULL)) + return false; + + torture_comment(torture, "Testing logon on pipe2\n"); + if (!test_netlogon_ex_ops(p2, torture, credentials2, NULL)) + return false; + + torture_comment(torture, "Again on pipe1\n"); + if (!test_netlogon_ex_ops(p1, torture, credentials1, NULL)) + return false; + + torture_comment(torture, "Again on pipe2\n"); + if (!test_netlogon_ex_ops(p2, torture, credentials2, NULL)) + return false; + + torture_leave_domain(torture, join_ctx); + return true; +} + +struct torture_schannel_bench; + +struct torture_schannel_bench_conn { + struct torture_schannel_bench *s; + int index; + struct cli_credentials *wks_creds; + struct dcerpc_pipe *pipe; + struct netr_LogonSamLogonEx r; + struct netr_NetworkInfo ninfo; + TALLOC_CTX *tmp; + uint64_t total; + uint32_t count; +}; + +struct torture_schannel_bench { + struct torture_context *tctx; + bool progress; + int timelimit; + int nprocs; + int nconns; + struct torture_schannel_bench_conn *conns; + struct test_join *join_ctx1; + struct cli_credentials *wks_creds1; + struct test_join *join_ctx2; + struct cli_credentials *wks_creds2; + struct cli_credentials *user1_creds; + struct cli_credentials *user2_creds; + struct dcerpc_binding *b; + NTSTATUS error; + uint64_t total; + uint32_t count; + bool stopped; +}; + +#if 0 +static void torture_schannel_bench_connected(struct composite_context *c) +{ + struct torture_schannel_bench_conn *conn = + (struct torture_schannel_bench_conn *)c->async.private_data; + struct torture_schannel_bench *s = talloc_get_type(conn->s, + struct torture_schannel_bench); + + s->error = dcerpc_pipe_connect_b_recv(c, s->conns, &conn->pipe); + torture_comment(s->tctx, "conn[%u]: %s\n", conn->index, nt_errstr(s->error)); + if (NT_STATUS_IS_OK(s->error)) { + s->nconns++; + } +} +#endif + +static void torture_schannel_bench_recv(struct tevent_req *subreq); + +static bool torture_schannel_bench_start(struct torture_schannel_bench_conn *conn) +{ + struct torture_schannel_bench *s = conn->s; + NTSTATUS status; + DATA_BLOB names_blob, chal, lm_resp, nt_resp; + int flags = CLI_CRED_NTLM_AUTH; + struct tevent_req *subreq; + struct cli_credentials *user_creds; + + if (conn->total % 2) { + user_creds = s->user1_creds; + } else { + user_creds = s->user2_creds; + } + + if (lpcfg_client_lanman_auth(s->tctx->lp_ctx)) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (lpcfg_client_ntlmv2_auth(s->tctx->lp_ctx)) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + + talloc_free(conn->tmp); + conn->tmp = talloc_new(s); + ZERO_STRUCT(conn->ninfo); + ZERO_STRUCT(conn->r); + + cli_credentials_get_ntlm_username_domain(user_creds, conn->tmp, + &conn->ninfo.identity_info.account_name.string, + &conn->ninfo.identity_info.domain_name.string); + + generate_random_buffer(conn->ninfo.challenge, + sizeof(conn->ninfo.challenge)); + chal = data_blob_const(conn->ninfo.challenge, + sizeof(conn->ninfo.challenge)); + + names_blob = NTLMv2_generate_names_blob(conn->tmp, + cli_credentials_get_workstation(conn->wks_creds), + cli_credentials_get_domain(conn->wks_creds)); + + status = cli_credentials_get_ntlm_response(user_creds, conn->tmp, + &flags, + chal, + NULL, /* server_timestamp */ + names_blob, + &lm_resp, &nt_resp, + NULL, NULL); + torture_assert_ntstatus_ok(s->tctx, status, + "cli_credentials_get_ntlm_response failed"); + + conn->ninfo.lm.data = lm_resp.data; + conn->ninfo.lm.length = lm_resp.length; + + conn->ninfo.nt.data = nt_resp.data; + conn->ninfo.nt.length = nt_resp.length; + + conn->ninfo.identity_info.parameter_control = 0; + conn->ninfo.identity_info.logon_id = 0; + conn->ninfo.identity_info.workstation.string = cli_credentials_get_workstation(conn->wks_creds); + + conn->r.in.server_name = talloc_asprintf(conn->tmp, "\\\\%s", dcerpc_server_name(conn->pipe)); + conn->r.in.computer_name = cli_credentials_get_workstation(conn->wks_creds); + conn->r.in.logon_level = NetlogonNetworkInformation; + conn->r.in.logon = talloc(conn->tmp, union netr_LogonLevel); + conn->r.in.logon->network = &conn->ninfo; + conn->r.in.flags = talloc(conn->tmp, uint32_t); + conn->r.in.validation_level = 2; + conn->r.out.validation = talloc(conn->tmp, union netr_Validation); + conn->r.out.authoritative = talloc(conn->tmp, uint8_t); + conn->r.out.flags = conn->r.in.flags; + + subreq = dcerpc_netr_LogonSamLogonEx_r_send(s, s->tctx->ev, + conn->pipe->binding_handle, + &conn->r); + torture_assert(s->tctx, subreq, "Failed to setup LogonSamLogonEx request"); + + tevent_req_set_callback(subreq, torture_schannel_bench_recv, conn); + + return true; +} + +static void torture_schannel_bench_recv(struct tevent_req *subreq) +{ + bool ret; + struct torture_schannel_bench_conn *conn = + (struct torture_schannel_bench_conn *)tevent_req_callback_data_void(subreq); + struct torture_schannel_bench *s = talloc_get_type(conn->s, + struct torture_schannel_bench); + + s->error = dcerpc_netr_LogonSamLogonEx_r_recv(subreq, subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(s->error)) { + return; + } + + conn->total++; + conn->count++; + + if (s->stopped) { + return; + } + + ret = torture_schannel_bench_start(conn); + if (!ret) { + s->error = NT_STATUS_INTERNAL_ERROR; + } +} + +/* + test multiple schannel connection in parallel + */ +bool torture_rpc_schannel_bench1(struct torture_context *torture) +{ + bool ret = true; + NTSTATUS status; + const char *binding = torture_setting_string(torture, "binding", NULL); + struct torture_schannel_bench *s; + struct timeval start; + struct timeval end; + int i; + const char *tmp; + + s = talloc_zero(torture, struct torture_schannel_bench); + s->tctx = torture; + s->progress = torture_setting_bool(torture, "progress", true); + s->timelimit = torture_setting_int(torture, "timelimit", 10); + s->nprocs = torture_setting_int(torture, "nprocs", 4); + s->conns = talloc_zero_array(s, struct torture_schannel_bench_conn, s->nprocs); + + s->user1_creds = cli_credentials_shallow_copy(s, + samba_cmdline_get_creds()); + tmp = torture_setting_string(s->tctx, "extra_user1", NULL); + if (tmp) { + cli_credentials_parse_string(s->user1_creds, tmp, CRED_SPECIFIED); + } + s->user2_creds = cli_credentials_shallow_copy(s, + samba_cmdline_get_creds()); + tmp = torture_setting_string(s->tctx, "extra_user2", NULL); + if (tmp) { + cli_credentials_parse_string(s->user1_creds, tmp, CRED_SPECIFIED); + } + + s->join_ctx1 = torture_join_domain(s->tctx, talloc_asprintf(s, "%sb", TEST_MACHINE_NAME), + ACB_WSTRUST, &s->wks_creds1); + torture_assert(torture, s->join_ctx1 != NULL, + "Failed to join domain with acct_flags=ACB_WSTRUST"); + s->join_ctx2 = torture_join_domain(s->tctx, talloc_asprintf(s, "%sc", TEST_MACHINE_NAME), + ACB_WSTRUST, &s->wks_creds2); + torture_assert(torture, s->join_ctx2 != NULL, + "Failed to join domain with acct_flags=ACB_WSTRUST"); + + cli_credentials_set_kerberos_state(s->wks_creds1, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + cli_credentials_set_kerberos_state(s->wks_creds2, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + + for (i=0; i < s->nprocs; i++) { + struct cli_credentials *wks = s->wks_creds1; + + if ((i % 2) && (torture_setting_bool(torture, "multijoin", false))) { + wks = s->wks_creds2; + } + + s->conns[i].s = s; + s->conns[i].index = i; + s->conns[i].wks_creds = cli_credentials_shallow_copy(s->conns, wks); + cli_credentials_set_netlogon_creds(s->conns[i].wks_creds, NULL); + } + + status = dcerpc_parse_binding(s, binding, &s->b); + torture_assert_ntstatus_ok(torture, status, "Bad binding string"); + + status = dcerpc_binding_set_flags(s->b, DCERPC_SCHANNEL | DCERPC_SIGN, + DCERPC_AUTH_OPTIONS); + torture_assert_ntstatus_ok(torture, status, "set flags"); + + torture_comment(torture, "Opening %d connections in parallel\n", s->nprocs); + for (i=0; i < s->nprocs; i++) { +#if 1 + s->error = dcerpc_pipe_connect_b(s->conns, &s->conns[i].pipe, s->b, + &ndr_table_netlogon, + s->conns[i].wks_creds, + torture->ev, torture->lp_ctx); + torture_assert_ntstatus_ok(torture, s->error, "Failed to connect with schannel"); +#else + /* + * This path doesn't work against windows, + * because of windows drops the connections + * which haven't reached a session setup yet + * + * The same as the reset on zero vc stuff. + */ + struct composite_context *c; + c = dcerpc_pipe_connect_b_send(s->conns, s->b, + &ndr_table_netlogon, + s->conns[i].wks_creds, + torture->ev, + torture->lp_ctx); + torture_assert(torture, c != NULL, "Failed to setup connect"); + c->async.fn = torture_schannel_bench_connected; + c->async.private_data = &s->conns[i]; + } + + while (NT_STATUS_IS_OK(s->error) && s->nprocs != s->nconns) { + int ev_ret = tevent_loop_once(torture->ev); + torture_assert(torture, ev_ret == 0, "tevent_loop_once failed"); +#endif + } + torture_assert_ntstatus_ok(torture, s->error, "Failed establish a connect"); + + /* + * Change the workstation password after establishing the netlogon + * schannel connections to prove that existing connections are not + * affected by a wks pwchange. + */ + + { + struct netr_ServerPasswordSet pwset; + char *password = generate_random_password(s->join_ctx1, 8, 255); + struct netlogon_creds_CredentialState *creds_state; + struct dcerpc_pipe *net_pipe; + struct netr_Authenticator credential, return_authenticator; + struct samr_Password new_password; + + status = dcerpc_pipe_connect_b(s, &net_pipe, s->b, + &ndr_table_netlogon, + s->wks_creds1, + torture->ev, torture->lp_ctx); + + torture_assert_ntstatus_ok(torture, status, + "dcerpc_pipe_connect_b failed"); + + pwset.in.server_name = talloc_asprintf( + net_pipe, "\\\\%s", dcerpc_server_name(net_pipe)); + pwset.in.computer_name = + cli_credentials_get_workstation(s->wks_creds1); + pwset.in.account_name = talloc_asprintf( + net_pipe, "%s$", pwset.in.computer_name); + pwset.in.secure_channel_type = SEC_CHAN_WKSTA; + pwset.in.credential = &credential; + pwset.in.new_password = &new_password; + pwset.out.return_authenticator = &return_authenticator; + + E_md4hash(password, new_password.hash); + + creds_state = cli_credentials_get_netlogon_creds( + s->wks_creds1); + netlogon_creds_des_encrypt(creds_state, &new_password); + netlogon_creds_client_authenticator(creds_state, &credential); + + torture_assert_ntstatus_ok(torture, dcerpc_netr_ServerPasswordSet_r(net_pipe->binding_handle, torture, &pwset), + "ServerPasswordSet failed"); + torture_assert_ntstatus_ok(torture, pwset.out.result, + "ServerPasswordSet failed"); + + if (!netlogon_creds_client_check(creds_state, + &pwset.out.return_authenticator->cred)) { + torture_comment(torture, "Credential chaining failed\n"); + } + + cli_credentials_set_password(s->wks_creds1, password, + CRED_SPECIFIED); + + talloc_free(net_pipe); + + /* Just as a test, connect with the new creds */ + + cli_credentials_set_netlogon_creds(s->wks_creds1, NULL); + + status = dcerpc_pipe_connect_b(s, &net_pipe, s->b, + &ndr_table_netlogon, + s->wks_creds1, + torture->ev, torture->lp_ctx); + + torture_assert_ntstatus_ok(torture, status, + "dcerpc_pipe_connect_b failed"); + + talloc_free(net_pipe); + } + + torture_comment(torture, "Start looping LogonSamLogonEx on %d connections for %d secs\n", + s->nprocs, s->timelimit); + for (i=0; i < s->nprocs; i++) { + ret = torture_schannel_bench_start(&s->conns[i]); + torture_assert(torture, ret, "Failed to setup LogonSamLogonEx"); + } + + start = timeval_current(); + end = timeval_add(&start, s->timelimit, 0); + + while (NT_STATUS_IS_OK(s->error) && !timeval_expired(&end)) { + int ev_ret = tevent_loop_once(torture->ev); + torture_assert(torture, ev_ret == 0, "tevent_loop_once failed"); + } + torture_assert_ntstatus_ok(torture, s->error, "Failed some request"); + s->stopped = true; + talloc_free(s->conns); + + for (i=0; i < s->nprocs; i++) { + s->total += s->conns[i].total; + } + + torture_comment(torture, + "Total ops[%llu] (%u ops/s)\n", + (unsigned long long)s->total, + (unsigned)s->total/s->timelimit); + + torture_leave_domain(torture, s->join_ctx1); + torture_leave_domain(torture, s->join_ctx2); + return true; +} diff --git a/source4/torture/rpc/session_key.c b/source4/torture/rpc/session_key.c new file mode 100644 index 0000000..96f9965 --- /dev/null +++ b/source4/torture/rpc/session_key.c @@ -0,0 +1,250 @@ +/* + Unix SMB/CIFS implementation. + test suite for lsa rpc operations + + Copyright (C) Andrew Tridgell 2003 + 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 "librpc/gen_ndr/ndr_lsa_c.h" + +#include "libcli/auth/libcli_auth.h" +#include "torture/rpc/torture_rpc.h" +#include "lib/cmdline/cmdline.h" +#include "param/param.h" + +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; +} + +static bool test_CreateSecret_basic(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + struct policy_handle *sec_handle) +{ + NTSTATUS status; + struct lsa_CreateSecret r; + struct lsa_SetSecret r3; + struct lsa_QuerySecret r4; + struct lsa_DATA_BUF buf1; + struct lsa_DATA_BUF_PTR bufp1; + DATA_BLOB enc_key; + DATA_BLOB session_key; + NTTIME old_mtime, new_mtime; + DATA_BLOB blob1; + const char *secret1 = "abcdef12345699qwerty"; + char *secret2; + char *secname; + struct dcerpc_binding_handle *b = p->binding_handle; + + secname = talloc_asprintf(tctx, "torturesecret-%08x", (unsigned int)random()); + + torture_comment(tctx, "Testing CreateSecret of %s\n", secname); + + init_lsa_String(&r.in.name, secname); + + r.in.handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.sec_handle = sec_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_CreateSecret_r(b, tctx, &r), + "CreateSecret failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "CreateSecret failed"); + + status = dcerpc_fetch_session_key(p, &session_key); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_fetch_session_key failed"); + + enc_key = sess_encrypt_string(secret1, &session_key); + + r3.in.sec_handle = sec_handle; + r3.in.new_val = &buf1; + r3.in.old_val = NULL; + r3.in.new_val->data = enc_key.data; + r3.in.new_val->length = enc_key.length; + r3.in.new_val->size = enc_key.length; + + torture_comment(tctx, "Testing SetSecret\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r3), + "SetSecret failed"); + torture_assert_ntstatus_ok(tctx, r3.out.result, "SetSecret failed"); + + r3.in.sec_handle = sec_handle; + r3.in.new_val = &buf1; + r3.in.old_val = NULL; + r3.in.new_val->data = enc_key.data; + r3.in.new_val->length = enc_key.length; + r3.in.new_val->size = enc_key.length; + + /* break the encrypted data */ + enc_key.data[0]++; + + torture_comment(tctx, "Testing SetSecret with broken key\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_SetSecret_r(b, tctx, &r3), + "SetSecret failed"); + torture_assert_ntstatus_equal(tctx, r3.out.result, NT_STATUS_UNKNOWN_REVISION, + "SetSecret should have failed UNKNOWN_REVISION"); + + data_blob_free(&enc_key); + + ZERO_STRUCT(new_mtime); + ZERO_STRUCT(old_mtime); + + /* fetch the secret back again */ + r4.in.sec_handle = sec_handle; + r4.in.new_val = &bufp1; + r4.in.new_mtime = &new_mtime; + r4.in.old_val = NULL; + r4.in.old_mtime = NULL; + + bufp1.buf = NULL; + + torture_comment(tctx, "Testing QuerySecret\n"); + torture_assert_ntstatus_ok(tctx, dcerpc_lsa_QuerySecret_r(b, tctx, &r4), + "QuerySecret failed"); + torture_assert_ntstatus_ok(tctx, r4.out.result, "QuerySecret failed"); + if (r4.out.new_val == NULL || r4.out.new_val->buf == NULL) + torture_fail(tctx, "No secret buffer returned"); + blob1.data = r4.out.new_val->buf->data; + blob1.length = r4.out.new_val->buf->size; + + secret2 = sess_decrypt_string(tctx, &blob1, &session_key); + + torture_assert_str_equal(tctx, secret1, secret2, "Returned secret invalid"); + + return true; +} + +struct secret_settings { + uint32_t bindoptions; + bool keyexchange; + bool ntlm2; + bool lm_key; +}; + +static bool test_secrets(struct torture_context *torture, const void *_data) +{ + struct dcerpc_pipe *p; + struct policy_handle *handle; + struct dcerpc_binding *binding; + const struct secret_settings *settings = + (const struct secret_settings *)_data; + NTSTATUS status; + struct dcerpc_binding_handle *b; + struct policy_handle sec_handle = {0}; + bool ok; + + lpcfg_set_cmdline(torture->lp_ctx, "ntlmssp client:keyexchange", settings->keyexchange?"True":"False"); + lpcfg_set_cmdline(torture->lp_ctx, "ntlmssp_client:ntlm2", settings->ntlm2?"True":"False"); + lpcfg_set_cmdline(torture->lp_ctx, "ntlmssp_client:lm_key", settings->lm_key?"True":"False"); + + torture_assert_ntstatus_ok(torture, torture_rpc_binding(torture, &binding), + "Getting bindoptions"); + + status = dcerpc_binding_set_flags(binding, settings->bindoptions, 0); + torture_assert_ntstatus_ok(torture, status, "dcerpc_binding_set_flags"); + + status = dcerpc_pipe_connect_b(torture, &p, binding, + &ndr_table_lsarpc, + samba_cmdline_get_creds(), + torture->ev, + torture->lp_ctx); + + torture_assert_ntstatus_ok(torture, status, "connect"); + b = p->binding_handle; + + if (!test_lsa_OpenPolicy2(b, torture, &handle)) { + talloc_free(p); + return false; + } + + torture_assert(torture, handle, "OpenPolicy2 failed. This test cannot run against this server"); + + ok = test_CreateSecret_basic(p, torture, handle, &sec_handle); + + if (is_valid_policy_hnd(&sec_handle)) { + struct lsa_DeleteObject d; + + d.in.handle = &sec_handle; + d.out.handle = &sec_handle; + + status = dcerpc_lsa_DeleteObject_r(b, torture, &d); + if (!NT_STATUS_IS_OK(status) || + !NT_STATUS_IS_OK(d.out.result)) { + torture_warning(torture, + "Failed to delete secrets object"); + } + } + + talloc_free(p); + return ok; +} + +static struct torture_tcase *add_test(struct torture_suite *suite, uint32_t bindoptions, + bool keyexchange, bool ntlm2, bool lm_key) +{ + char *name = NULL; + struct secret_settings *settings; + + settings = talloc_zero(suite, struct secret_settings); + settings->bindoptions = bindoptions; + + if (bindoptions == DCERPC_PUSH_BIGENDIAN) + name = talloc_strdup(suite, "bigendian"); + else if (bindoptions == DCERPC_SEAL) + name = talloc_strdup(suite, "seal"); + else if (bindoptions == 0) + name = talloc_strdup(suite, "none"); + else + name = talloc_strdup(suite, "unknown"); + + name = talloc_asprintf_append_buffer(name, " keyexchange:%s", keyexchange?"yes":"no"); + settings->keyexchange = keyexchange; + + name = talloc_asprintf_append_buffer(name, " ntlm2:%s", ntlm2?"yes":"no"); + settings->ntlm2 = ntlm2; + + name = talloc_asprintf_append_buffer(name, " lm_key:%s", lm_key?"yes":"no"); + settings->lm_key = lm_key; + + return torture_suite_add_simple_tcase_const(suite, name, test_secrets, + settings); +} + +static const bool bool_vals[] = { true, false }; + +/* TEST session key correctness by pushing and pulling secrets */ +struct torture_suite *torture_rpc_lsa_secrets(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "lsa.secrets"); + int keyexchange, ntlm2, lm_key; + + for (keyexchange = 0; keyexchange < ARRAY_SIZE(bool_vals); keyexchange++) { + for (ntlm2 = 0; ntlm2 < ARRAY_SIZE(bool_vals); ntlm2++) { + for (lm_key = 0; lm_key < ARRAY_SIZE(bool_vals); lm_key++) { + add_test(suite, DCERPC_PUSH_BIGENDIAN, bool_vals[keyexchange], bool_vals[ntlm2], + bool_vals[lm_key]); + add_test(suite, DCERPC_SEAL, bool_vals[keyexchange], bool_vals[ntlm2], bool_vals[lm_key]); + add_test(suite, 0, bool_vals[keyexchange], bool_vals[ntlm2], bool_vals[lm_key]); + } + } + } + + return suite; +} diff --git a/source4/torture/rpc/spoolss.c b/source4/torture/rpc/spoolss.c new file mode 100644 index 0000000..dd0d458 --- /dev/null +++ b/source4/torture/rpc/spoolss.c @@ -0,0 +1,11704 @@ +/* + Unix SMB/CIFS implementation. + test suite for spoolss rpc operations + + Copyright (C) Tim Potter 2003 + Copyright (C) Stefan Metzmacher 2005 + Copyright (C) Jelmer Vernooij 2007 + Copyright (C) Guenther Deschner 2009-2011,2013 + + 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 "torture/torture.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_winreg_c.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "lib/registry/registry.h" +#include "libcli/libcli.h" +#include "libcli/raw/raw_proto.h" +#include "libcli/resolve/resolve.h" +#include "libcli/smb2/smb2.h" +#include "libcli/smb2/smb2_calls.h" +#include "lib/cmdline/cmdline.h" +#include "system/filesys.h" +#include "torture/ndr/ndr.h" +#include "torture/smb2/proto.h" + +#define TORTURE_WELLKNOWN_PRINTER "torture_wkn_printer" +#define TORTURE_PRINTER "torture_printer" +#define TORTURE_WELLKNOWN_PRINTER_EX "torture_wkn_printer_ex" +#define TORTURE_PRINTER_EX "torture_printer_ex" +#define TORTURE_DRIVER "torture_driver" +#define TORTURE_DRIVER_ADD "torture_driver_add" +#define TORTURE_DRIVER_EX "torture_driver_ex" +#define TORTURE_DRIVER_ADOBE "torture_driver_adobe" +#define TORTURE_DRIVER_EX_ADOBE "torture_driver_ex_adobe" +#define TORTURE_DRIVER_ADOBE_CUPSADDSMB "torture_driver_adobe_cupsaddsmb" +#define TORTURE_DRIVER_TIMESTAMPS "torture_driver_timestamps" +#define TORTURE_DRIVER_DELETER "torture_driver_deleter" +#define TORTURE_DRIVER_COPY_DIR "torture_driver_copy_from_directory" +#define TORTURE_DRIVER_DELETERIN "torture_driver_deleterin" +#define TORTURE_PRINTER_STATIC1 "print1" + +#define TOP_LEVEL_PRINT_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print" +#define TOP_LEVEL_PRINT_PRINTERS_KEY TOP_LEVEL_PRINT_KEY "\\Printers" +#define TOP_LEVEL_CONTROL_KEY "SYSTEM\\CurrentControlSet\\Control\\Print" +#define TOP_LEVEL_CONTROL_FORMS_KEY TOP_LEVEL_CONTROL_KEY "\\Forms" +#define TOP_LEVEL_CONTROL_PRINTERS_KEY TOP_LEVEL_CONTROL_KEY "\\Printers" +#define TOP_LEVEL_CONTROL_ENVIRONMENTS_KEY TOP_LEVEL_CONTROL_KEY "\\Environments" + +struct test_spoolss_context { + struct dcerpc_pipe *spoolss_pipe; + + /* server environment */ + const char *environment; + + /* print server handle */ + struct policy_handle server_handle; + + /* for EnumPorts */ + uint32_t port_count[3]; + union spoolss_PortInfo *ports[3]; + + /* for EnumPrinterDrivers */ + uint32_t driver_count[9]; + union spoolss_DriverInfo *drivers[9]; + + /* for EnumMonitors */ + uint32_t monitor_count[3]; + union spoolss_MonitorInfo *monitors[3]; + + /* for EnumPrintProcessors */ + uint32_t print_processor_count[2]; + union spoolss_PrintProcessorInfo *print_processors[2]; + + /* for EnumPrinters */ + uint32_t printer_count[6]; + union spoolss_PrinterInfo *printers[6]; +}; + +struct torture_driver_context { + struct { + const char *driver_directory; + const char *environment; + } local; + struct { + const char *driver_directory; + const char *driver_upload_directory; + const char *environment; + } remote; + struct spoolss_AddDriverInfo8 info8; + bool ex; +}; + +struct torture_printer_context { + struct dcerpc_pipe *spoolss_pipe; + struct spoolss_SetPrinterInfo2 info2; + struct torture_driver_context driver; + bool ex; + bool wellknown; + bool added_driver; + bool have_driver; + struct spoolss_DeviceMode *devmode; + struct policy_handle handle; +}; + +static bool upload_printer_driver(struct torture_context *tctx, + const char *server_name, + struct torture_driver_context *d); +static bool remove_printer_driver(struct torture_context *tctx, + const char *server_name, + struct torture_driver_context *d); +static bool fillup_printserver_info(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct torture_driver_context *d); +static bool test_AddPrinterDriver_args_level_3(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir); + +#define COMPARE_STRING(tctx, c,r,e) \ + torture_assert_str_equal(tctx, c.e, r.e, "invalid value") + +/* not every compiler supports __typeof__() */ +#if (__GNUC__ >= 3) +#define _CHECK_FIELD_SIZE(c,r,e,type) do {\ + if (sizeof(__typeof__(c.e)) != sizeof(type)) { \ + torture_fail(tctx, #c "." #e "field is not " #type "\n"); \ + }\ + if (sizeof(__typeof__(r.e)) != sizeof(type)) { \ + torture_fail(tctx, #r "." #e "field is not " #type "\n"); \ + }\ +} while(0) +#else +#define _CHECK_FIELD_SIZE(c,r,e,type) do {} while(0) +#endif + +#define COMPARE_UINT32(tctx, c, r, e) do {\ + _CHECK_FIELD_SIZE(c, r, e, uint32_t); \ + torture_assert_int_equal(tctx, c.e, r.e, "invalid value"); \ +} while(0) + +#define COMPARE_UINT64(tctx, c, r, e) do {\ + _CHECK_FIELD_SIZE(c, r, e, uint64_t); \ + torture_assert_int_equal(tctx, c.e, r.e, "invalid value"); \ +} while(0) + + +#define COMPARE_NTTIME(tctx, c, r, e) do {\ + _CHECK_FIELD_SIZE(c, r, e, NTTIME); \ + torture_assert_int_equal(tctx, c.e, r.e, "invalid value"); \ +} while(0) + +#define COMPARE_STRING_ARRAY(tctx, c,r,e) do {\ + int __i; \ + if (!c.e && !r.e) { \ + break; \ + } \ + if (c.e && !r.e) { \ + torture_fail(tctx, #r "." #e " field is NULL and " #c "." #e " is not\n"); \ + } \ + if (!c.e && r.e) { \ + torture_fail(tctx, #c "." #e " field is NULL and " #r "." #e " is not\n"); \ + } \ + for (__i=0;c.e[__i] != NULL; __i++) { \ + torture_assert_str_equal(tctx, c.e[__i], r.e[__i], "invalid value"); \ + } \ +} while(0) + +#define CHECK_ALIGN(size, n) do {\ + if (size % n) {\ + torture_warning(tctx, "%d is *NOT* %d byte aligned, should be %d",\ + size, n, size + n - (size % n));\ + }\ +} while(0) + +#define DO_ROUND(size, n) (((size)+((n)-1)) & ~((n)-1)) + +#define CHECK_NEEDED_SIZE_ENUM_LEVEL(fn, info, level, count, needed, align) do { \ + if (torture_setting_bool(tctx, "spoolss_check_size", false)) {\ + uint32_t size = ndr_size_##fn##_info(tctx, level, count, info);\ + uint32_t round_size = DO_ROUND(size, align);\ + if (round_size != needed) {\ + torture_warning(tctx, __location__": "#fn" level %d (count: %d) got unexpected needed size: %d, we calculated: %d", level, count, needed, round_size);\ + CHECK_ALIGN(size, align);\ + }\ + }\ +} while(0) + +#define CHECK_NEEDED_SIZE_ENUM(fn, info, count, needed, align) do { \ + if (torture_setting_bool(tctx, "spoolss_check_size", false)) {\ + uint32_t size = ndr_size_##fn##_info(tctx, count, info);\ + uint32_t round_size = DO_ROUND(size, align);\ + if (round_size != needed) {\ + torture_warning(tctx, __location__": "#fn" (count: %d) got unexpected needed size: %d, we calculated: %d", count, needed, round_size);\ + CHECK_ALIGN(size, align);\ + }\ + }\ +} while(0) + +#define CHECK_NEEDED_SIZE_LEVEL(fn, info, level, needed, align) do { \ + if (torture_setting_bool(tctx, "spoolss_check_size", false)) {\ + uint32_t size = ndr_size_##fn(info, level, 0);\ + uint32_t round_size = DO_ROUND(size, align);\ + if (round_size != needed) {\ + torture_warning(tctx, __location__": "#fn" level %d got unexpected needed size: %d, we calculated: %d", level, needed, round_size);\ + CHECK_ALIGN(size, align);\ + }\ + }\ +} while(0) + +static bool PrinterInfo_to_SetPrinterInfo(struct torture_context *tctx, + const union spoolss_PrinterInfo *i, + uint32_t level, + union spoolss_SetPrinterInfo *s) +{ + switch (level) { + case 0: + s->info0 = talloc(tctx, struct spoolss_SetPrinterInfo0); + break; + case 2: + s->info2 = talloc(tctx, struct spoolss_SetPrinterInfo2); + s->info2->servername = i->info2.servername; + s->info2->printername = i->info2.printername; + s->info2->sharename = i->info2.sharename; + s->info2->portname = i->info2.portname; + s->info2->drivername = i->info2.drivername; + s->info2->comment = i->info2.comment; + s->info2->location = i->info2.location; + s->info2->devmode_ptr = 0; + s->info2->sepfile = i->info2.sepfile; + s->info2->printprocessor = i->info2.printprocessor; + s->info2->datatype = i->info2.datatype; + s->info2->parameters = i->info2.parameters; + s->info2->secdesc_ptr = 0; + s->info2->attributes = i->info2.attributes; + s->info2->priority = i->info2.priority; + s->info2->defaultpriority = i->info2.defaultpriority; + s->info2->starttime = i->info2.starttime; + s->info2->untiltime = i->info2.untiltime; + s->info2->status = i->info2.status; + s->info2->cjobs = i->info2.cjobs; + s->info2->averageppm = i->info2.averageppm; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + default: + return false; + } + + return true; +} + +static bool test_OpenPrinter_server(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *server_handle) +{ + NTSTATUS status; + struct spoolss_OpenPrinter op; + struct dcerpc_binding_handle *b = p->binding_handle; + + op.in.printername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + op.in.datatype = NULL; + op.in.devmode_ctr.devmode= NULL; + op.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + op.out.handle = server_handle; + + torture_comment(tctx, "Testing OpenPrinter(%s)\n", op.in.printername); + + status = dcerpc_spoolss_OpenPrinter_r(b, tctx, &op); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_OpenPrinter failed"); + torture_assert_werr_ok(tctx, op.out.result, "dcerpc_spoolss_OpenPrinter failed"); + + return true; +} + +static bool test_EnumPorts(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + struct spoolss_EnumPorts r; + uint16_t levels[] = { 1, 2 }; + int i, j; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_PortInfo *info; + + r.in.servername = ""; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPorts level %u\n", r.in.level); + + status = dcerpc_spoolss_EnumPorts_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumPorts failed"); + if (W_ERROR_IS_OK(r.out.result)) { + /* TODO: do some more checks here */ + continue; + } + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "EnumPorts unexpected return code"); + + blob = data_blob_talloc_zero(ctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_EnumPorts_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumPorts failed"); + + torture_assert_werr_ok(tctx, r.out.result, "EnumPorts failed"); + + torture_assert(tctx, info, "EnumPorts returned no info"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPorts, info, r.in.level, count, needed, 4); + + ctx->port_count[level] = count; + ctx->ports[level] = info; + } + + for (i=1;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + int old_level = levels[i-1]; + torture_assert_int_equal(tctx, ctx->port_count[level], ctx->port_count[old_level], + "EnumPorts invalid value"); + } + /* if the array sizes are not the same we would maybe segfault in the following code */ + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + for (j=0;j<ctx->port_count[level];j++) { + union spoolss_PortInfo *cur = &ctx->ports[level][j]; + union spoolss_PortInfo *ref = &ctx->ports[2][j]; + switch (level) { + case 1: + COMPARE_STRING(tctx, cur->info1, ref->info2, port_name); + break; + case 2: + /* level 2 is our reference, and it makes no sense to compare it to itself */ + break; + } + } + } + + return true; +} + +static bool test_GetPrintProcessorDirectory(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + NTSTATUS status; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_GetPrintProcessorDirectory r; + struct { + uint16_t level; + const char *server; + } levels[] = {{ + .level = 1, + .server = NULL + },{ + .level = 1, + .server = "" + },{ + .level = 78, + .server = "" + },{ + .level = 1, + .server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)) + },{ + .level = 1024, + .server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)) + } + }; + int i; + uint32_t needed; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i].level; + DATA_BLOB blob; + + r.in.server = levels[i].server; + r.in.environment = ctx->environment; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_comment(tctx, "Testing GetPrintProcessorDirectory level %u\n", r.in.level); + + status = dcerpc_spoolss_GetPrintProcessorDirectory_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_spoolss_GetPrintProcessorDirectory failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "GetPrintProcessorDirectory unexpected return code"); + + blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_GetPrintProcessorDirectory_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_GetPrintProcessorDirectory failed"); + + torture_assert_werr_ok(tctx, r.out.result, "GetPrintProcessorDirectory failed"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_PrintProcessorDirectoryInfo, r.out.info, r.in.level, needed, 2); + } + + return true; +} + + +static bool test_GetPrinterDriverDirectory(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + NTSTATUS status; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_GetPrinterDriverDirectory r; + struct { + uint16_t level; + const char *server; + } levels[] = {{ + .level = 1, + .server = NULL + },{ + .level = 1, + .server = "" + },{ + .level = 78, + .server = "" + },{ + .level = 1, + .server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)) + },{ + .level = 1024, + .server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)) + } + }; + int i; + uint32_t needed; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i].level; + DATA_BLOB blob; + + r.in.server = levels[i].server; + r.in.environment = ctx->environment; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_comment(tctx, "Testing GetPrinterDriverDirectory level %u\n", r.in.level); + + status = dcerpc_spoolss_GetPrinterDriverDirectory_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_spoolss_GetPrinterDriverDirectory failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "GetPrinterDriverDirectory unexpected return code"); + + blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_GetPrinterDriverDirectory_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_GetPrinterDriverDirectory failed"); + + torture_assert_werr_ok(tctx, r.out.result, "GetPrinterDriverDirectory failed"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_DriverDirectoryInfo, r.out.info, r.in.level, needed, 2); + } + + return true; +} + +static bool test_EnumPrinterDrivers_buffers(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + const char *environment, + uint32_t level, + uint32_t offered, + uint32_t *count_p, + union spoolss_DriverInfo **info_p) +{ + struct spoolss_EnumPrinterDrivers r; + uint32_t needed; + uint32_t count; + union spoolss_DriverInfo *info; + DATA_BLOB buffer; + + if (offered > 0) { + buffer = data_blob_talloc_zero(tctx, offered); + } + + r.in.server = server_name; + r.in.environment = environment; + r.in.level = level; + r.in.buffer = offered ? &buffer : NULL; + r.in.offered = offered; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrinterDrivers(%s) level %u, offered: %u\n", + r.in.environment, r.in.level, r.in.offered); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinterDrivers_r(b, tctx, &r), + "EnumPrinterDrivers failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinterDrivers_r(b, tctx, &r), + "EnumPrinterDrivers failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "EnumPrinterDrivers failed"); + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrinterDrivers, info, r.in.level, count, needed, 4); + + return true; + +} + + +static bool test_EnumPrinterDrivers_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + const char *environment, + uint32_t level, + uint32_t *count_p, + union spoolss_DriverInfo **info_p) +{ + return test_EnumPrinterDrivers_buffers(tctx, b, server_name, + environment, level, 0, + count_p, info_p); +} + +static bool test_EnumPrinterDrivers_findone(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + const char *environment, + uint32_t level, + const char *driver_name, + union spoolss_DriverInfo *info_p) +{ + uint32_t count; + union spoolss_DriverInfo *info; + int i; + const char *environment_ret = NULL; + + torture_assert(tctx, + test_EnumPrinterDrivers_args(tctx, b, server_name, environment, level, &count, &info), + "failed to enumerate printer drivers"); + + for (i=0; i < count; i++) { + const char *driver_name_ret = ""; + switch (level) { + case 1: + driver_name_ret = info[i].info1.driver_name; + break; + case 2: + driver_name_ret = info[i].info2.driver_name; + environment_ret = info[i].info2.architecture; + break; + case 3: + driver_name_ret = info[i].info3.driver_name; + environment_ret = info[i].info3.architecture; + break; + case 4: + driver_name_ret = info[i].info4.driver_name; + environment_ret = info[i].info4.architecture; + break; + case 5: + driver_name_ret = info[i].info5.driver_name; + environment_ret = info[i].info5.architecture; + break; + case 6: + driver_name_ret = info[i].info6.driver_name; + environment_ret = info[i].info6.architecture; + break; + case 7: + driver_name_ret = info[i].info7.driver_name; + break; + case 8: + driver_name_ret = info[i].info8.driver_name; + environment_ret = info[i].info8.architecture; + break; + default: + break; + } + if (environment_ret) { + torture_assert_str_equal(tctx, environment, environment_ret, "architecture mismatch"); + } + if (strequal(driver_name, driver_name_ret)) { + if (info_p) { + *info_p = info[i]; + } + return true; + } + } + + return false; +} + +static bool test_EnumPrinterDrivers(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + uint16_t levels[] = { 1, 2, 3, 4, 5, 6, 8 }; + uint16_t buffer_sizes[] = { 0, 1024, 6040, 0xffff }; + int i, j, a; + + /* FIXME: gd, come back and fix "" as server, and handle + * priority of returned error codes in torture test and samba 3 + * server */ + const char *server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + const char *environments[2]; + + environments[0] = SPOOLSS_ARCHITECTURE_ALL; + environments[1] = ctx->environment; + + for (a=0;a<ARRAY_SIZE(environments);a++) { + + for (i=0;i<ARRAY_SIZE(buffer_sizes);i++) { + torture_assert(tctx, + test_EnumPrinterDrivers_buffers(tctx, b, server_name, + environments[a], 3, + buffer_sizes[i], + NULL, NULL), + "failed to enumerate drivers"); + } + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + uint32_t count; + union spoolss_DriverInfo *info; + + torture_assert(tctx, + test_EnumPrinterDrivers_args(tctx, b, server_name, environments[a], level, &count, &info), + "failed to enumerate drivers"); + + ctx->driver_count[level] = count; + ctx->drivers[level] = info; + } + + for (i=1;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + int old_level = levels[i-1]; + + torture_assert_int_equal(tctx, ctx->driver_count[level], ctx->driver_count[old_level], + "EnumPrinterDrivers invalid value"); + } + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + + for (j=0;j<ctx->driver_count[level - 1];j++) { + union spoolss_DriverInfo *cur = &ctx->drivers[level - 1][j]; + union spoolss_DriverInfo *ref = &ctx->drivers[8][j]; + + switch (level) { + case 1: + COMPARE_STRING(tctx, cur->info1, ref->info8, driver_name); + break; + case 2: + COMPARE_UINT32(tctx, cur->info2, ref->info8, version); + COMPARE_STRING(tctx, cur->info2, ref->info8, driver_name); + COMPARE_STRING(tctx, cur->info2, ref->info8, architecture); + COMPARE_STRING(tctx, cur->info2, ref->info8, driver_path); + COMPARE_STRING(tctx, cur->info2, ref->info8, data_file); + COMPARE_STRING(tctx, cur->info2, ref->info8, config_file); + break; + case 3: + COMPARE_UINT32(tctx, cur->info3, ref->info8, version); + COMPARE_STRING(tctx, cur->info3, ref->info8, driver_name); + COMPARE_STRING(tctx, cur->info3, ref->info8, architecture); + COMPARE_STRING(tctx, cur->info3, ref->info8, driver_path); + COMPARE_STRING(tctx, cur->info3, ref->info8, data_file); + COMPARE_STRING(tctx, cur->info3, ref->info8, config_file); + COMPARE_STRING(tctx, cur->info3, ref->info8, help_file); + COMPARE_STRING_ARRAY(tctx, cur->info3, ref->info8, dependent_files); + COMPARE_STRING(tctx, cur->info3, ref->info8, monitor_name); + COMPARE_STRING(tctx, cur->info3, ref->info8, default_datatype); + break; + case 4: + COMPARE_UINT32(tctx, cur->info4, ref->info8, version); + COMPARE_STRING(tctx, cur->info4, ref->info8, driver_name); + COMPARE_STRING(tctx, cur->info4, ref->info8, architecture); + COMPARE_STRING(tctx, cur->info4, ref->info8, driver_path); + COMPARE_STRING(tctx, cur->info4, ref->info8, data_file); + COMPARE_STRING(tctx, cur->info4, ref->info8, config_file); + COMPARE_STRING(tctx, cur->info4, ref->info8, help_file); + COMPARE_STRING_ARRAY(tctx, cur->info4, ref->info8, dependent_files); + COMPARE_STRING(tctx, cur->info4, ref->info8, monitor_name); + COMPARE_STRING(tctx, cur->info4, ref->info8, default_datatype); + COMPARE_STRING_ARRAY(tctx, cur->info4, ref->info8, previous_names); + break; + case 5: + COMPARE_UINT32(tctx, cur->info5, ref->info8, version); + COMPARE_STRING(tctx, cur->info5, ref->info8, driver_name); + COMPARE_STRING(tctx, cur->info5, ref->info8, architecture); + COMPARE_STRING(tctx, cur->info5, ref->info8, driver_path); + COMPARE_STRING(tctx, cur->info5, ref->info8, data_file); + COMPARE_STRING(tctx, cur->info5, ref->info8, config_file); + /*COMPARE_UINT32(tctx, cur->info5, ref->info8, driver_attributes);*/ + /*COMPARE_UINT32(tctx, cur->info5, ref->info8, config_version);*/ + /*TODO: ! COMPARE_UINT32(tctx, cur->info5, ref->info8, driver_version); */ + break; + case 6: + COMPARE_UINT32(tctx, cur->info6, ref->info8, version); + COMPARE_STRING(tctx, cur->info6, ref->info8, driver_name); + COMPARE_STRING(tctx, cur->info6, ref->info8, architecture); + COMPARE_STRING(tctx, cur->info6, ref->info8, driver_path); + COMPARE_STRING(tctx, cur->info6, ref->info8, data_file); + COMPARE_STRING(tctx, cur->info6, ref->info8, config_file); + COMPARE_STRING(tctx, cur->info6, ref->info8, help_file); + COMPARE_STRING_ARRAY(tctx, cur->info6, ref->info8, dependent_files); + COMPARE_STRING(tctx, cur->info6, ref->info8, monitor_name); + COMPARE_STRING(tctx, cur->info6, ref->info8, default_datatype); + COMPARE_STRING_ARRAY(tctx, cur->info6, ref->info8, previous_names); + COMPARE_NTTIME(tctx, cur->info6, ref->info8, driver_date); + COMPARE_UINT64(tctx, cur->info6, ref->info8, driver_version); + COMPARE_STRING(tctx, cur->info6, ref->info8, manufacturer_name); + COMPARE_STRING(tctx, cur->info6, ref->info8, manufacturer_url); + COMPARE_STRING(tctx, cur->info6, ref->info8, hardware_id); + COMPARE_STRING(tctx, cur->info6, ref->info8, provider); + break; + case 8: + /* level 8 is our reference, and it makes no sense to compare it to itself */ + break; + } + } + } + } + + return true; +} + +static bool test_EnumMonitors(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + struct spoolss_EnumMonitors r; + uint16_t levels[] = { 1, 2 }; + int i, j; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_MonitorInfo *info; + + r.in.servername = ""; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumMonitors level %u\n", r.in.level); + + status = dcerpc_spoolss_EnumMonitors_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumMonitors failed"); + if (W_ERROR_IS_OK(r.out.result)) { + /* TODO: do some more checks here */ + continue; + } + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "EnumMonitors failed"); + + blob = data_blob_talloc_zero(ctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_EnumMonitors_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumMonitors failed"); + + torture_assert_werr_ok(tctx, r.out.result, "EnumMonitors failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumMonitors, info, r.in.level, count, needed, 4); + + ctx->monitor_count[level] = count; + ctx->monitors[level] = info; + } + + for (i=1;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + int old_level = levels[i-1]; + torture_assert_int_equal(tctx, ctx->monitor_count[level], ctx->monitor_count[old_level], + "EnumMonitors invalid value"); + } + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + for (j=0;j<ctx->monitor_count[level];j++) { + union spoolss_MonitorInfo *cur = &ctx->monitors[level][j]; + union spoolss_MonitorInfo *ref = &ctx->monitors[2][j]; + switch (level) { + case 1: + COMPARE_STRING(tctx, cur->info1, ref->info2, monitor_name); + break; + case 2: + torture_assert_str_equal(tctx, ref->info2.environment, ctx->environment, "invalid environment"); + /* level 2 is our reference, and it makes no sense to compare it to itself */ + break; + } + } + } + + return true; +} + +static bool test_EnumPrintProcessors_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *environment, + uint32_t level, + uint32_t *count_p, + union spoolss_PrintProcessorInfo **info_p, + WERROR expected_result) +{ + struct spoolss_EnumPrintProcessors r; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_PrintProcessorInfo *info; + + r.in.servername = ""; + r.in.environment = environment; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrintProcessors(%s) level %u\n", + r.in.environment, r.in.level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrintProcessors_r(b, tctx, &r), + "EnumPrintProcessors failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrintProcessors_r(b, tctx, &r), + "EnumPrintProcessors failed"); + } + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "EnumPrintProcessors failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrintProcessors, info, level, count, needed, 4); + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + return true; +} + +static bool test_EnumPrintProcessors(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + uint16_t levels[] = {0, 1, 2, 3, 32, 256 }; + uint16_t ok[] = {0, 1, 0, 0, 0, 0 }; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_EnumPrintProcessors_level(tctx, b, "phantasy", 1, NULL, NULL, WERR_INVALID_ENVIRONMENT), + "test_EnumPrintProcessors_level failed"); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + union spoolss_PrintProcessorInfo *info; + uint32_t count; + WERROR expected_result = ok[i] ? WERR_OK : WERR_INVALID_LEVEL; + + torture_assert(tctx, + test_EnumPrintProcessors_level(tctx, b, ctx->environment, levels[i], &count, &info, expected_result), + "test_EnumPrintProcessors_level failed"); + } + + return true; +} + +static bool test_EnumPrintProcessorDataTypes_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *print_processor_name, + uint32_t level, + uint32_t *count_p, + union spoolss_PrintProcDataTypesInfo **info_p, + WERROR expected_result) +{ + struct spoolss_EnumPrintProcessorDataTypes r; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_PrintProcDataTypesInfo *info; + + r.in.servername = ""; + r.in.print_processor_name = print_processor_name; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrintProcessorDataTypes(%s) level %u\n", + r.in.print_processor_name, r.in.level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrintProcessorDataTypes_r(b, tctx, &r), + "EnumPrintProcessorDataTypes failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrintProcessorDataTypes_r(b, tctx, &r), + "EnumPrintProcessorDataTypes failed"); + } + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "EnumPrintProcessorDataTypes failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrintProcessorDataTypes, info, level, count, needed, 4); + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + return true; +} + +static bool test_EnumPrintProcessorDataTypes(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + uint16_t levels[] = {0, 1, 2, 3, 32, 256 }; + uint16_t ok[] = {0, 1, 0, 0, 0, 0 }; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_EnumPrintProcessorDataTypes_level(tctx, b, NULL, 1, NULL, NULL, WERR_UNKNOWN_PRINTPROCESSOR), + "test_EnumPrintProcessorDataTypes_level failed"); + + torture_assert(tctx, + test_EnumPrintProcessorDataTypes_level(tctx, b, "nonexisting", 1, NULL, NULL, WERR_UNKNOWN_PRINTPROCESSOR), + "test_EnumPrintProcessorDataTypes_level failed"); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + uint32_t count; + union spoolss_PrintProcDataTypesInfo *info; + WERROR expected_result = ok[i] ? WERR_OK : WERR_INVALID_LEVEL; + + torture_assert(tctx, + test_EnumPrintProcessorDataTypes_level(tctx, b, "winprint", level, &count, &info, expected_result), + "test_EnumPrintProcessorDataTypes_level failed"); + } + + { + union spoolss_PrintProcessorInfo *info; + uint32_t count; + + torture_assert(tctx, + test_EnumPrintProcessors_level(tctx, b, ctx->environment, 1, &count, &info, WERR_OK), + "test_EnumPrintProcessors_level failed"); + + for (i=0; i < count; i++) { + torture_assert(tctx, + test_EnumPrintProcessorDataTypes_level(tctx, b, info[i].info1.print_processor_name, 1, NULL, NULL, WERR_OK), + "test_EnumPrintProcessorDataTypes_level failed"); + } + } + + + return true; +} + +static bool test_EnumPrinters(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_EnumPrinters r; + NTSTATUS status; + uint16_t levels[] = { 0, 1, 2, 4, 5 }; + int i, j; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + DATA_BLOB blob; + uint32_t needed; + uint32_t count; + union spoolss_PrinterInfo *info; + + r.in.flags = PRINTER_ENUM_LOCAL; + r.in.server = ""; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrinters level %u\n", r.in.level); + + status = dcerpc_spoolss_EnumPrinters_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumPrinters failed"); + if (W_ERROR_IS_OK(r.out.result)) { + /* TODO: do some more checks here */ + continue; + } + torture_assert_werr_equal(tctx, r.out.result, WERR_INSUFFICIENT_BUFFER, + "EnumPrinters unexpected return code"); + + blob = data_blob_talloc_zero(ctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_EnumPrinters_r(b, ctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EnumPrinters failed"); + + torture_assert_werr_ok(tctx, r.out.result, "EnumPrinters failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrinters, info, r.in.level, count, needed, 4); + + ctx->printer_count[level] = count; + ctx->printers[level] = info; + } + + for (i=1;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + int old_level = levels[i-1]; + torture_assert_int_equal(tctx, ctx->printer_count[level], ctx->printer_count[old_level], + "EnumPrinters invalid value"); + } + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int level = levels[i]; + for (j=0;j<ctx->printer_count[level];j++) { + union spoolss_PrinterInfo *cur = &ctx->printers[level][j]; + union spoolss_PrinterInfo *ref = &ctx->printers[2][j]; + switch (level) { + case 0: + COMPARE_STRING(tctx, cur->info0, ref->info2, printername); + COMPARE_STRING(tctx, cur->info0, ref->info2, servername); + COMPARE_UINT32(tctx, cur->info0, ref->info2, cjobs); + /*COMPARE_UINT32(tctx, cur->info0, ref->info2, total_jobs); + COMPARE_UINT32(tctx, cur->info0, ref->info2, total_bytes); + COMPARE_SPOOLSS_TIME(cur->info0, ref->info2, spoolss_Time time); + COMPARE_UINT32(tctx, cur->info0, ref->info2, global_counter); + COMPARE_UINT32(tctx, cur->info0, ref->info2, total_pages); + COMPARE_UINT32(tctx, cur->info0, ref->info2, version); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown10); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown11); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown12); + COMPARE_UINT32(tctx, cur->info0, ref->info2, session_counter); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown14); + COMPARE_UINT32(tctx, cur->info0, ref->info2, printer_errors); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown16); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown17); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown18); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown19); + COMPARE_UINT32(tctx, cur->info0, ref->info2, change_id); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown21);*/ + COMPARE_UINT32(tctx, cur->info0, ref->info2, status); + /*COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown23); + COMPARE_UINT32(tctx, cur->info0, ref->info2, c_setprinter); + COMPARE_UINT16(cur->info0, ref->info2, unknown25); + COMPARE_UINT16(cur->info0, ref->info2, unknown26); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown27); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown28); + COMPARE_UINT32(tctx, cur->info0, ref->info2, unknown29);*/ + break; + case 1: + /*COMPARE_UINT32(tctx, cur->info1, ref->info2, flags);*/ + /*COMPARE_STRING(tctx, cur->info1, ref->info2, name);*/ + /*COMPARE_STRING(tctx, cur->info1, ref->info2, description);*/ + COMPARE_STRING(tctx, cur->info1, ref->info2, comment); + break; + case 2: + /* level 2 is our reference, and it makes no sense to compare it to itself */ + break; + case 4: + COMPARE_STRING(tctx, cur->info4, ref->info2, printername); + COMPARE_STRING(tctx, cur->info4, ref->info2, servername); + COMPARE_UINT32(tctx, cur->info4, ref->info2, attributes); + break; + case 5: + COMPARE_STRING(tctx, cur->info5, ref->info2, printername); + COMPARE_STRING(tctx, cur->info5, ref->info2, portname); + COMPARE_UINT32(tctx, cur->info5, ref->info2, attributes); + /*COMPARE_UINT32(tctx, cur->info5, ref->info2, device_not_selected_timeout); + COMPARE_UINT32(tctx, cur->info5, ref->info2, transmission_retry_timeout);*/ + break; + } + } + } + + /* TODO: + * - verify that the port of a printer was in the list returned by EnumPorts + */ + + return true; +} + +static bool test_GetPrinterDriver2(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *driver_name, + const char *environment); + +bool test_GetPrinter_level_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + WERROR expected_werror, + union spoolss_PrinterInfo *info) +{ + struct spoolss_GetPrinter r; + uint32_t needed; + + r.in.handle = handle; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_comment(tctx, "Testing GetPrinter level %u\n", r.in.level); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_GetPrinter_r(b, tctx, &r), + "GetPrinter failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_GetPrinter_r(b, tctx, &r), + "GetPrinter failed"); + } + + torture_assert_werr_equal(tctx, + r.out.result, expected_werror, + "GetPrinter failed"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_PrinterInfo, r.out.info, r.in.level, needed, 4); + + if (info && r.out.info) { + *info = *r.out.info; + } + + return true; +} + +bool test_GetPrinter_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + union spoolss_PrinterInfo *info) +{ + return test_GetPrinter_level_exp(tctx, b, handle, level, WERR_OK, info); +} + +static bool test_GetPrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *environment) +{ + uint32_t levels[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + int i; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + union spoolss_PrinterInfo info; + + ZERO_STRUCT(info); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, levels[i], &info), + "failed to call GetPrinter"); + + if ((levels[i] == 2) && info.info2.drivername && strlen(info.info2.drivername)) { + torture_assert(tctx, + test_GetPrinterDriver2(tctx, b, handle, info.info2.drivername, environment), + "failed to call test_GetPrinterDriver2"); + } + } + + return true; +} + +static bool test_SetPrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct spoolss_SetPrinterInfoCtr *info_ctr, + struct spoolss_DevmodeContainer *devmode_ctr, + struct sec_desc_buf *secdesc_ctr, + enum spoolss_PrinterControl command) +{ + struct spoolss_SetPrinter r; + + r.in.handle = handle; + r.in.info_ctr = info_ctr; + r.in.devmode_ctr = devmode_ctr; + r.in.secdesc_ctr = secdesc_ctr; + r.in.command = command; + + torture_comment(tctx, "Testing SetPrinter level %d\n", r.in.info_ctr->level); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), + "failed to call SetPrinter"); + torture_assert(tctx, (W_ERROR_EQUAL(r.out.result, WERR_OK) + || W_ERROR_EQUAL(r.out.result, WERR_IO_PENDING)), + "SetPrinter failed"); + + return true; +} + +static bool test_SetPrinter_errors(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct spoolss_SetPrinter r; + uint16_t levels[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + int i; + + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info_ctr.level = 0; + info_ctr.info.info0 = NULL; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.in.command = 0; + + torture_comment(tctx, "Testing SetPrinter all zero\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), + "failed to call SetPrinter"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "failed to call SetPrinter"); + + again: + for (i=0; i < ARRAY_SIZE(levels); i++) { + + struct spoolss_SetPrinterInfo0 info0; + struct spoolss_SetPrinterInfo1 info1; + struct spoolss_SetPrinterInfo2 info2; + struct spoolss_SetPrinterInfo3 info3; + struct spoolss_SetPrinterInfo4 info4; + struct spoolss_SetPrinterInfo5 info5; + struct spoolss_SetPrinterInfo6 info6; + struct spoolss_SetPrinterInfo7 info7; + struct spoolss_SetPrinterInfo8 info8; + struct spoolss_SetPrinterInfo9 info9; + + + info_ctr.level = levels[i]; + switch (levels[i]) { + case 0: + ZERO_STRUCT(info0); + info_ctr.info.info0 = &info0; + break; + case 1: + ZERO_STRUCT(info1); + info_ctr.info.info1 = &info1; + break; + case 2: + ZERO_STRUCT(info2); + info_ctr.info.info2 = &info2; + break; + case 3: + ZERO_STRUCT(info3); + info_ctr.info.info3 = &info3; + break; + case 4: + ZERO_STRUCT(info4); + info_ctr.info.info4 = &info4; + break; + case 5: + ZERO_STRUCT(info5); + info_ctr.info.info5 = &info5; + break; + case 6: + ZERO_STRUCT(info6); + info_ctr.info.info6 = &info6; + break; + case 7: + ZERO_STRUCT(info7); + info_ctr.info.info7 = &info7; + break; + case 8: + ZERO_STRUCT(info8); + info_ctr.info.info8 = &info8; + break; + case 9: + ZERO_STRUCT(info9); + info_ctr.info.info9 = &info9; + break; + } + + torture_comment(tctx, "Testing SetPrinter level %d, command %d\n", + info_ctr.level, r.in.command); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), + "failed to call SetPrinter"); + + switch (r.in.command) { + case SPOOLSS_PRINTER_CONTROL_UNPAUSE: /* 0 */ + /* is ignored for all levels other then 0 */ + if (info_ctr.level > 0) { + /* ignored then */ + break; + } + + FALL_THROUGH; + case SPOOLSS_PRINTER_CONTROL_PAUSE: /* 1 */ + case SPOOLSS_PRINTER_CONTROL_RESUME: /* 2 */ + case SPOOLSS_PRINTER_CONTROL_PURGE: /* 3 */ + if (info_ctr.level > 0) { + /* is invalid for all levels other then 0 */ + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PRINTER_COMMAND, + "unexpected error code returned"); + continue; + } else { + torture_assert_werr_ok(tctx, r.out.result, + "failed to call SetPrinter with non 0 command"); + continue; + } + break; + + case SPOOLSS_PRINTER_CONTROL_SET_STATUS: /* 4 */ + /* FIXME: gd needs further investigation */ + default: + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PRINTER_COMMAND, + "unexpected error code returned"); + continue; + } + + switch (info_ctr.level) { + case 1: + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, + "unexpected error code returned"); + break; + case 2: + torture_assert_werr_equal(tctx, r.out.result, WERR_UNKNOWN_PRINTER_DRIVER, + "unexpected error code returned"); + break; + case 3: + case 4: + case 5: + case 7: + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "unexpected error code returned"); + break; + case 9: + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "unexpected error code returned"); + break; + default: + torture_assert_werr_ok(tctx, r.out.result, + "failed to call SetPrinter"); + break; + } + } + + if (r.in.command < 5) { + r.in.command++; + goto again; + } + + return true; +} + +static void clear_info2(struct spoolss_SetPrinterInfoCtr *r) +{ + if ((r->level == 2) && (r->info.info2)) { + r->info.info2->secdesc_ptr = 0; + r->info.info2->devmode_ptr = 0; + } +} + +static bool test_PrinterInfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_SetPrinter s; + struct spoolss_GetPrinter q; + struct spoolss_GetPrinter q0; + struct spoolss_SetPrinterInfoCtr info_ctr; + union spoolss_PrinterInfo info; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + uint32_t needed; + bool ret = true; + int i; + + uint32_t status_list[] = { + /* these do not stick + PRINTER_STATUS_PAUSED, + PRINTER_STATUS_ERROR, + PRINTER_STATUS_PENDING_DELETION, */ + PRINTER_STATUS_PAPER_JAM, + PRINTER_STATUS_PAPER_OUT, + PRINTER_STATUS_MANUAL_FEED, + PRINTER_STATUS_PAPER_PROBLEM, + PRINTER_STATUS_OFFLINE, + PRINTER_STATUS_IO_ACTIVE, + PRINTER_STATUS_BUSY, + PRINTER_STATUS_PRINTING, + PRINTER_STATUS_OUTPUT_BIN_FULL, + PRINTER_STATUS_NOT_AVAILABLE, + PRINTER_STATUS_WAITING, + PRINTER_STATUS_PROCESSING, + PRINTER_STATUS_INITIALIZING, + PRINTER_STATUS_WARMING_UP, + PRINTER_STATUS_TONER_LOW, + PRINTER_STATUS_NO_TONER, + PRINTER_STATUS_PAGE_PUNT, + PRINTER_STATUS_USER_INTERVENTION, + PRINTER_STATUS_OUT_OF_MEMORY, + PRINTER_STATUS_DOOR_OPEN, + PRINTER_STATUS_SERVER_UNKNOWN, + PRINTER_STATUS_POWER_SAVE, + /* these do not stick + 0x02000000, + 0x04000000, + 0x08000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000 */ + }; + uint32_t default_attribute = PRINTER_ATTRIBUTE_LOCAL; + uint32_t attribute_list[] = { + PRINTER_ATTRIBUTE_QUEUED, + /* fails with WERR_INVALID_DATATYPE: + PRINTER_ATTRIBUTE_DIRECT, */ + /* does not stick + PRINTER_ATTRIBUTE_DEFAULT, */ + PRINTER_ATTRIBUTE_SHARED, + /* does not stick + PRINTER_ATTRIBUTE_NETWORK, */ + PRINTER_ATTRIBUTE_HIDDEN, + PRINTER_ATTRIBUTE_LOCAL, + PRINTER_ATTRIBUTE_ENABLE_DEVQ, + PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS, + PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST, + PRINTER_ATTRIBUTE_WORK_OFFLINE, + /* does not stick + PRINTER_ATTRIBUTE_ENABLE_BIDI, */ + /* fails with WERR_INVALID_DATATYPE: + PRINTER_ATTRIBUTE_RAW_ONLY, */ + /* these do not stick + PRINTER_ATTRIBUTE_PUBLISHED, + PRINTER_ATTRIBUTE_FAX, + PRINTER_ATTRIBUTE_TS, + 0x00010000, + 0x00020000, + 0x00040000, + 0x00080000, + 0x00100000, + 0x00200000, + 0x00400000, + 0x00800000, + 0x01000000, + 0x02000000, + 0x04000000, + 0x08000000, + 0x10000000, + 0x20000000, + 0x40000000, + 0x80000000 */ + }; + + torture_skip(tctx, "Printer Info test is currently broken, skipping"); + + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + s.in.handle = handle; + s.in.command = 0; + s.in.info_ctr = &info_ctr; + s.in.devmode_ctr = &devmode_ctr; + s.in.secdesc_ctr = &secdesc_ctr; + + q.in.handle = handle; + q.out.info = &info; + q0 = q; + +#define TESTGETCALL(call, r) \ + r.in.buffer = NULL; \ + r.in.offered = 0;\ + r.out.needed = &needed; \ + status = dcerpc_spoolss_ ##call## _r(b, tctx, &r); \ + if (!NT_STATUS_IS_OK(status)) { \ + torture_comment(tctx, #call " level %u failed - %s (%s)\n", \ + r.in.level, nt_errstr(status), __location__); \ + ret = false; \ + break; \ + }\ + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) {\ + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); \ + r.in.buffer = &blob; \ + r.in.offered = needed; \ + }\ + status = dcerpc_spoolss_ ##call## _r(b, tctx, &r); \ + if (!NT_STATUS_IS_OK(status)) { \ + torture_comment(tctx, #call " level %u failed - %s (%s)\n", \ + r.in.level, nt_errstr(status), __location__); \ + ret = false; \ + break; \ + } \ + if (!W_ERROR_IS_OK(r.out.result)) { \ + torture_comment(tctx, #call " level %u failed - %s (%s)\n", \ + r.in.level, win_errstr(r.out.result), __location__); \ + ret = false; \ + break; \ + } + + +#define TESTSETCALL_EXP(call, r, err) \ + clear_info2(&info_ctr);\ + status = dcerpc_spoolss_ ##call## _r(b, tctx, &r); \ + if (!NT_STATUS_IS_OK(status)) { \ + torture_comment(tctx, #call " level %u failed - %s (%s)\n", \ + r.in.info_ctr->level, nt_errstr(status), __location__); \ + ret = false; \ + break; \ + } \ + if (!W_ERROR_IS_OK(err)) { \ + if (!W_ERROR_EQUAL(err, r.out.result)) { \ + torture_comment(tctx, #call " level %u failed - %s, expected %s (%s)\n", \ + r.in.info_ctr->level, win_errstr(r.out.result), win_errstr(err), __location__); \ + ret = false; \ + } \ + break; \ + } \ + if (!W_ERROR_IS_OK(r.out.result)) { \ + torture_comment(tctx, #call " level %u failed - %s (%s)\n", \ + r.in.info_ctr->level, win_errstr(r.out.result), __location__); \ + ret = false; \ + break; \ + } + +#define TESTSETCALL(call, r) \ + TESTSETCALL_EXP(call, r, WERR_OK) + +#define STRING_EQUAL(s1, s2, field) \ + if ((s1 && !s2) || (s2 && !s1) || strcmp(s1, s2)) { \ + torture_comment(tctx, "Failed to set %s to '%s' (%s)\n", \ + #field, s2, __location__); \ + ret = false; \ + break; \ + } + +#define MEM_EQUAL(s1, s2, length, field) \ + if ((s1 && !s2) || (s2 && !s1) || memcmp(s1, s2, length)) { \ + torture_comment(tctx, "Failed to set %s to '%s' (%s)\n", \ + #field, (const char *)s2, __location__); \ + ret = false; \ + break; \ + } + +#define INT_EQUAL(i1, i2, field) \ + if (i1 != i2) { \ + torture_comment(tctx, "Failed to set %s to 0x%llx - got 0x%llx (%s)\n", \ + #field, (unsigned long long)i2, (unsigned long long)i1, __location__); \ + ret = false; \ + break; \ + } + +#define SD_EQUAL(sd1, sd2, field) \ + if (!security_descriptor_equal(sd1, sd2)) { \ + torture_comment(tctx, "Failed to set %s (%s)\n", \ + #field, __location__); \ + ret = false; \ + break; \ + } + +#define TEST_PRINTERINFO_STRING_EXP_ERR(lvl1, field1, lvl2, field2, value, err) do { \ + void *p; \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTGETCALL(GetPrinter, q) \ + info_ctr.level = lvl1; \ + p = (void *)&q.out.info->info ## lvl1; \ + info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \ + info_ctr.info.info ## lvl1->field1 = value;\ + TESTSETCALL_EXP(SetPrinter, s, err) \ + info_ctr.info.info ## lvl1->field1 = ""; \ + TESTGETCALL(GetPrinter, q) \ + info_ctr.info.info ## lvl1->field1 = value; \ + STRING_EQUAL(info_ctr.info.info ## lvl1->field1, value, field1); \ + q.in.level = lvl2; \ + TESTGETCALL(GetPrinter, q) \ + p = (void *)&q.out.info->info ## lvl2; \ + info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)p; \ + STRING_EQUAL(info_ctr.info.info ## lvl2->field2, value, field2); \ + } while (0) + +#define TEST_PRINTERINFO_STRING(lvl1, field1, lvl2, field2, value) do { \ + TEST_PRINTERINFO_STRING_EXP_ERR(lvl1, field1, lvl2, field2, value, WERR_OK); \ + } while (0); + +#define TEST_PRINTERINFO_INT_EXP(lvl1, field1, lvl2, field2, value, exp_value) do { \ + void *p; \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTGETCALL(GetPrinter, q) \ + info_ctr.level = lvl1; \ + p = (void *)&q.out.info->info ## lvl1; \ + info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \ + info_ctr.info.info ## lvl1->field1 = value; \ + TESTSETCALL(SetPrinter, s) \ + info_ctr.info.info ## lvl1->field1 = 0; \ + TESTGETCALL(GetPrinter, q) \ + p = (void *)&q.out.info->info ## lvl1; \ + info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \ + INT_EQUAL(info_ctr.info.info ## lvl1->field1, exp_value, field1); \ + q.in.level = lvl2; \ + TESTGETCALL(GetPrinter, q) \ + p = (void *)&q.out.info->info ## lvl2; \ + info_ctr.info.info ## lvl2 = (struct spoolss_SetPrinterInfo ## lvl2 *)p; \ + INT_EQUAL(info_ctr.info.info ## lvl2->field2, exp_value, field1); \ + } while (0) + +#define TEST_PRINTERINFO_INT(lvl1, field1, lvl2, field2, value) do { \ + TEST_PRINTERINFO_INT_EXP(lvl1, field1, lvl2, field2, value, value); \ + } while (0) + + q0.in.level = 0; + do { TESTGETCALL(GetPrinter, q0) } while (0); + + TEST_PRINTERINFO_STRING(2, comment, 1, comment, "xx2-1 comment"); + TEST_PRINTERINFO_STRING(2, comment, 2, comment, "xx2-2 comment"); + + /* level 0 printername does not stick */ +/* TEST_PRINTERINFO_STRING(2, printername, 0, printername, "xx2-0 printer"); */ + TEST_PRINTERINFO_STRING(2, printername, 1, name, "xx2-1 printer"); + TEST_PRINTERINFO_STRING(2, printername, 2, printername, "xx2-2 printer"); + TEST_PRINTERINFO_STRING(2, printername, 4, printername, "xx2-4 printer"); + TEST_PRINTERINFO_STRING(2, printername, 5, printername, "xx2-5 printer"); +/* TEST_PRINTERINFO_STRING(4, printername, 0, printername, "xx4-0 printer"); */ + TEST_PRINTERINFO_STRING(4, printername, 1, name, "xx4-1 printer"); + TEST_PRINTERINFO_STRING(4, printername, 2, printername, "xx4-2 printer"); + TEST_PRINTERINFO_STRING(4, printername, 4, printername, "xx4-4 printer"); + TEST_PRINTERINFO_STRING(4, printername, 5, printername, "xx4-5 printer"); +/* TEST_PRINTERINFO_STRING(5, printername, 0, printername, "xx5-0 printer"); */ + TEST_PRINTERINFO_STRING(5, printername, 1, name, "xx5-1 printer"); + TEST_PRINTERINFO_STRING(5, printername, 2, printername, "xx5-2 printer"); + TEST_PRINTERINFO_STRING(5, printername, 4, printername, "xx5-4 printer"); + TEST_PRINTERINFO_STRING(5, printername, 5, printername, "xx5-5 printer"); + + /* servername can be set but does not stick + TEST_PRINTERINFO_STRING(2, servername, 0, servername, "xx2-0 servername"); + TEST_PRINTERINFO_STRING(2, servername, 2, servername, "xx2-2 servername"); + TEST_PRINTERINFO_STRING(2, servername, 4, servername, "xx2-4 servername"); + */ + + /* passing an invalid port will result in WERR_UNKNOWN_PORT */ + TEST_PRINTERINFO_STRING_EXP_ERR(2, portname, 2, portname, "xx2-2 portname", WERR_UNKNOWN_PORT); + TEST_PRINTERINFO_STRING_EXP_ERR(2, portname, 5, portname, "xx2-5 portname", WERR_UNKNOWN_PORT); + TEST_PRINTERINFO_STRING_EXP_ERR(5, portname, 2, portname, "xx5-2 portname", WERR_UNKNOWN_PORT); + TEST_PRINTERINFO_STRING_EXP_ERR(5, portname, 5, portname, "xx5-5 portname", WERR_UNKNOWN_PORT); + + TEST_PRINTERINFO_STRING(2, sharename, 2, sharename, "xx2-2 sharename"); + /* passing an invalid driver will result in WERR_UNKNOWN_PRINTER_DRIVER */ + TEST_PRINTERINFO_STRING_EXP_ERR(2, drivername, 2, drivername, "xx2-2 drivername", WERR_UNKNOWN_PRINTER_DRIVER); + TEST_PRINTERINFO_STRING(2, location, 2, location, "xx2-2 location"); + /* passing an invalid sepfile will result in WERR_INVALID_SEPARATOR_FILE */ + TEST_PRINTERINFO_STRING_EXP_ERR(2, sepfile, 2, sepfile, "xx2-2 sepfile", WERR_INVALID_SEPARATOR_FILE); + /* passing an invalid printprocessor will result in WERR_UNKNOWN_PRINTPROCESSOR */ + TEST_PRINTERINFO_STRING_EXP_ERR(2, printprocessor, 2, printprocessor, "xx2-2 printprocessor", WERR_UNKNOWN_PRINTPROCESSOR); + TEST_PRINTERINFO_STRING(2, datatype, 2, datatype, "xx2-2 datatype"); + TEST_PRINTERINFO_STRING(2, parameters, 2, parameters, "xx2-2 parameters"); + + for (i=0; i < ARRAY_SIZE(attribute_list); i++) { +/* TEST_PRINTERINFO_INT_EXP(2, attributes, 1, flags, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); */ + TEST_PRINTERINFO_INT_EXP(2, attributes, 2, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(2, attributes, 4, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(2, attributes, 5, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); +/* TEST_PRINTERINFO_INT_EXP(4, attributes, 1, flags, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); */ + TEST_PRINTERINFO_INT_EXP(4, attributes, 2, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(4, attributes, 4, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(4, attributes, 5, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); +/* TEST_PRINTERINFO_INT_EXP(5, attributes, 1, flags, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); */ + TEST_PRINTERINFO_INT_EXP(5, attributes, 2, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(5, attributes, 4, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + TEST_PRINTERINFO_INT_EXP(5, attributes, 5, attributes, + attribute_list[i], + (attribute_list[i] | default_attribute) + ); + } + + for (i=0; i < ARRAY_SIZE(status_list); i++) { + /* level 2 sets do not stick + TEST_PRINTERINFO_INT(2, status, 0, status, status_list[i]); + TEST_PRINTERINFO_INT(2, status, 2, status, status_list[i]); + TEST_PRINTERINFO_INT(2, status, 6, status, status_list[i]); */ + TEST_PRINTERINFO_INT(6, status, 0, status, status_list[i]); + TEST_PRINTERINFO_INT(6, status, 2, status, status_list[i]); + TEST_PRINTERINFO_INT(6, status, 6, status, status_list[i]); + } + + /* priorities need to be between 0 and 99 + passing an invalid priority will result in WERR_INVALID_PRIORITY */ + TEST_PRINTERINFO_INT(2, priority, 2, priority, 0); + TEST_PRINTERINFO_INT(2, priority, 2, priority, 1); + TEST_PRINTERINFO_INT(2, priority, 2, priority, 99); + /* TEST_PRINTERINFO_INT(2, priority, 2, priority, 100); */ + TEST_PRINTERINFO_INT(2, defaultpriority,2, defaultpriority, 0); + TEST_PRINTERINFO_INT(2, defaultpriority,2, defaultpriority, 1); + TEST_PRINTERINFO_INT(2, defaultpriority,2, defaultpriority, 99); + /* TEST_PRINTERINFO_INT(2, defaultpriority,2, defaultpriority, 100); */ + + TEST_PRINTERINFO_INT(2, starttime, 2, starttime, __LINE__); + TEST_PRINTERINFO_INT(2, untiltime, 2, untiltime, __LINE__); + + /* does not stick + TEST_PRINTERINFO_INT(2, cjobs, 2, cjobs, __LINE__); + TEST_PRINTERINFO_INT(2, averageppm, 2, averageppm, __LINE__); */ + + /* does not stick + TEST_PRINTERINFO_INT(5, device_not_selected_timeout, 5, device_not_selected_timeout, __LINE__); + TEST_PRINTERINFO_INT(5, transmission_retry_timeout, 5, transmission_retry_timeout, __LINE__); */ + + /* FIXME: gd also test devmode and secdesc behavior */ + + { + /* verify composition of level 1 description field */ + const char *description; + const char *tmp; + + q0.in.level = 1; + do { TESTGETCALL(GetPrinter, q0) } while (0); + + description = talloc_strdup(tctx, q0.out.info->info1.description); + + q0.in.level = 2; + do { TESTGETCALL(GetPrinter, q0) } while (0); + + tmp = talloc_asprintf(tctx, "%s,%s,%s", + q0.out.info->info2.printername, + q0.out.info->info2.drivername, + q0.out.info->info2.location); + + do { STRING_EQUAL(description, tmp, "description")} while (0); + } + + return ret; +} + +static bool test_security_descriptor_equal(struct torture_context *tctx, + const struct security_descriptor *sd1, + const struct security_descriptor *sd2) +{ + if (sd1 == sd2) { + return true; + } + + if (!sd1 || !sd2) { + torture_comment(tctx, "%s\n", __location__); + return false; + } + + torture_assert_int_equal(tctx, sd1->revision, sd2->revision, "revision mismatch"); + torture_assert_int_equal(tctx, sd1->type, sd2->type, "type mismatch"); + + torture_assert_sid_equal(tctx, sd1->owner_sid, sd2->owner_sid, "owner mismatch"); + torture_assert_sid_equal(tctx, sd1->group_sid, sd2->group_sid, "group mismatch"); + + if (!security_acl_equal(sd1->sacl, sd2->sacl)) { + torture_comment(tctx, "%s: sacl mismatch\n", __location__); + NDR_PRINT_DEBUG(security_acl, sd1->sacl); + NDR_PRINT_DEBUG(security_acl, sd2->sacl); + return false; + } + if (!security_acl_equal(sd1->dacl, sd2->dacl)) { + torture_comment(tctx, "%s: dacl mismatch\n", __location__); + NDR_PRINT_DEBUG(security_acl, sd1->dacl); + NDR_PRINT_DEBUG(security_acl, sd2->dacl); + return false; + } + + return true; +} + +static bool test_sd_set_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + struct security_descriptor *sd) +{ + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + union spoolss_SetPrinterInfo sinfo; + union spoolss_PrinterInfo info; + struct spoolss_SetPrinterInfo3 info3; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + switch (level) { + case 2: { + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + torture_assert(tctx, PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), ""); + + info_ctr.level = 2; + info_ctr.info = sinfo; + + break; + } + case 3: { + + info3.sec_desc_ptr = 0; + + info_ctr.level = 3; + info_ctr.info.info3 = &info3; + + break; + } + default: + return false; + } + + secdesc_ctr.sd = sd; + + torture_assert(tctx, + test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), ""); + + return true; +} + +static bool test_PrinterInfo_SDs(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct security_descriptor *sd1, *sd2; + int i; + + /* just compare level 2 and level 3 */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + sd1 = info.info2.secdesc; + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 3, &info), ""); + + sd2 = info.info3.secdesc; + + torture_assert(tctx, test_security_descriptor_equal(tctx, sd1, sd2), + "SD level 2 != SD level 3"); + + + /* query level 2, set level 2, query level 2 */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + sd1 = info.info2.secdesc; + + torture_assert(tctx, test_sd_set_level(tctx, b, handle, 2, sd1), ""); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + sd2 = info.info2.secdesc; + if (sd1->type & SEC_DESC_DACL_DEFAULTED) { + torture_comment(tctx, "removing SEC_DESC_DACL_DEFAULTED\n"); + sd1->type &= ~SEC_DESC_DACL_DEFAULTED; + } + + torture_assert(tctx, test_security_descriptor_equal(tctx, sd1, sd2), + "SD level 2 != SD level 2 after SD has been set via level 2"); + + + /* query level 2, set level 3, query level 2 */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + sd1 = info.info2.secdesc; + + torture_assert(tctx, test_sd_set_level(tctx, b, handle, 3, sd1), ""); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + sd2 = info.info2.secdesc; + + torture_assert(tctx, test_security_descriptor_equal(tctx, sd1, sd2), + "SD level 2 != SD level 2 after SD has been set via level 3"); + + /* set modified sd level 3, query level 2 */ + + for (i=0; i < 93; i++) { + struct security_ace a; + const char *sid_string = talloc_asprintf(tctx, "S-1-5-32-9999%i", i); + a.type = SEC_ACE_TYPE_ACCESS_ALLOWED; + a.flags = 0; + a.size = 0; /* autogenerated */ + a.access_mask = 0; + a.trustee = *dom_sid_parse_talloc(tctx, sid_string); + torture_assert_ntstatus_ok(tctx, security_descriptor_dacl_add(sd1, &a), ""); + } + + torture_assert(tctx, test_sd_set_level(tctx, b, handle, 3, sd1), ""); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + sd2 = info.info2.secdesc; + + if (sd1->type & SEC_DESC_DACL_DEFAULTED) { + torture_comment(tctx, "removing SEC_DESC_DACL_DEFAULTED\n"); + sd1->type &= ~SEC_DESC_DACL_DEFAULTED; + } + + torture_assert(tctx, test_security_descriptor_equal(tctx, sd1, sd2), + "modified SD level 2 != SD level 2 after SD has been set via level 3"); + + + return true; +} + +/* + * wrapper call that saves original sd, runs tests, and restores sd + */ + +static bool test_PrinterInfo_SD(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct security_descriptor *sd; + bool ret = true; + + torture_comment(tctx, "Testing Printer Security Descriptors\n"); + + /* save original sd */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to get initial security descriptor"); + + sd = security_descriptor_copy(tctx, info.info2.secdesc); + + /* run tests */ + + ret = test_PrinterInfo_SDs(tctx, b, handle); + + /* restore original sd */ + + torture_assert(tctx, test_sd_set_level(tctx, b, handle, 3, sd), + "failed to restore initial security descriptor"); + + torture_comment(tctx, "Printer Security Descriptors test %s\n\n", + ret ? "succeeded" : "failed"); + + + return ret; +} + +static bool test_devmode_set_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + struct spoolss_DeviceMode *devmode) +{ + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + union spoolss_SetPrinterInfo sinfo; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + switch (level) { + case 2: { + union spoolss_PrinterInfo info; + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + torture_assert(tctx, PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), ""); + + info_ctr.level = 2; + info_ctr.info = sinfo; + + break; + } + case 8: { + struct spoolss_SetPrinterInfo8 info8; + + info8.devmode_ptr = 0; + + info_ctr.level = 8; + info_ctr.info.info8 = &info8; + + break; + } + default: + return false; + } + + devmode_ctr.devmode = devmode; + + torture_assert(tctx, + test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), ""); + + return true; +} + + +static bool test_devicemode_equal(struct torture_context *tctx, + const struct spoolss_DeviceMode *d1, + const struct spoolss_DeviceMode *d2) +{ + if (d1 == d2) { + return true; + } + + if (!d1 || !d2) { + torture_comment(tctx, "%s\n", __location__); + return false; + } + torture_assert_str_equal(tctx, d1->devicename, d2->devicename, "devicename mismatch"); + torture_assert_int_equal(tctx, d1->specversion, d2->specversion, "specversion mismatch"); + torture_assert_int_equal(tctx, d1->driverversion, d2->driverversion, "driverversion mismatch"); + torture_assert_int_equal(tctx, d1->size, d2->size, "size mismatch"); + torture_assert_int_equal(tctx, d1->__driverextra_length, d2->__driverextra_length, "__driverextra_length mismatch"); + torture_assert_int_equal(tctx, d1->fields, d2->fields, "fields mismatch"); + torture_assert_int_equal(tctx, d1->orientation, d2->orientation, "orientation mismatch"); + torture_assert_int_equal(tctx, d1->papersize, d2->papersize, "papersize mismatch"); + torture_assert_int_equal(tctx, d1->paperlength, d2->paperlength, "paperlength mismatch"); + torture_assert_int_equal(tctx, d1->paperwidth, d2->paperwidth, "paperwidth mismatch"); + torture_assert_int_equal(tctx, d1->scale, d2->scale, "scale mismatch"); + torture_assert_int_equal(tctx, d1->copies, d2->copies, "copies mismatch"); + torture_assert_int_equal(tctx, d1->defaultsource, d2->defaultsource, "defaultsource mismatch"); + torture_assert_int_equal(tctx, d1->printquality, d2->printquality, "printquality mismatch"); + torture_assert_int_equal(tctx, d1->color, d2->color, "color mismatch"); + torture_assert_int_equal(tctx, d1->duplex, d2->duplex, "duplex mismatch"); + torture_assert_int_equal(tctx, d1->yresolution, d2->yresolution, "yresolution mismatch"); + torture_assert_int_equal(tctx, d1->ttoption, d2->ttoption, "ttoption mismatch"); + torture_assert_int_equal(tctx, d1->collate, d2->collate, "collate mismatch"); + torture_assert_str_equal(tctx, d1->formname, d2->formname, "formname mismatch"); + torture_assert_int_equal(tctx, d1->logpixels, d2->logpixels, "logpixels mismatch"); + torture_assert_int_equal(tctx, d1->bitsperpel, d2->bitsperpel, "bitsperpel mismatch"); + torture_assert_int_equal(tctx, d1->pelswidth, d2->pelswidth, "pelswidth mismatch"); + torture_assert_int_equal(tctx, d1->pelsheight, d2->pelsheight, "pelsheight mismatch"); + torture_assert_int_equal(tctx, d1->displayflags, d2->displayflags, "displayflags mismatch"); + torture_assert_int_equal(tctx, d1->displayfrequency, d2->displayfrequency, "displayfrequency mismatch"); + torture_assert_int_equal(tctx, d1->icmmethod, d2->icmmethod, "icmmethod mismatch"); + torture_assert_int_equal(tctx, d1->icmintent, d2->icmintent, "icmintent mismatch"); + torture_assert_int_equal(tctx, d1->mediatype, d2->mediatype, "mediatype mismatch"); + torture_assert_int_equal(tctx, d1->dithertype, d2->dithertype, "dithertype mismatch"); + torture_assert_int_equal(tctx, d1->reserved1, d2->reserved1, "reserved1 mismatch"); + torture_assert_int_equal(tctx, d1->reserved2, d2->reserved2, "reserved2 mismatch"); + torture_assert_int_equal(tctx, d1->panningwidth, d2->panningwidth, "panningwidth mismatch"); + torture_assert_int_equal(tctx, d1->panningheight, d2->panningheight, "panningheight mismatch"); + torture_assert_data_blob_equal(tctx, d1->driverextra_data, d2->driverextra_data, "driverextra_data mismatch"); + + return true; +} + +static bool test_devicemode_full(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct spoolss_SetPrinter s; + struct spoolss_GetPrinter q; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo8 info8; + union spoolss_PrinterInfo info; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + uint32_t needed; + bool ret = true; + NTSTATUS status; + +#define TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, exp_value, expected_result) do { \ + torture_comment(tctx, "field test %d/%s vs %d/%s\n", lvl1, #field1, lvl2, #field2); \ + q.in.level = lvl1; \ + TESTGETCALL(GetPrinter, q) \ + info_ctr.level = lvl1; \ + if (lvl1 == 2) {\ + void *p = (void *)&q.out.info->info ## lvl1; \ + info_ctr.info.info ## lvl1 = (struct spoolss_SetPrinterInfo ## lvl1 *)p; \ + } else if (lvl1 == 8) {\ + info_ctr.info.info ## lvl1 = &info8; \ + }\ + devmode_ctr.devmode = q.out.info->info ## lvl1.devmode; \ + devmode_ctr.devmode->field1 = value; \ + TESTSETCALL_EXP(SetPrinter, s, expected_result) \ + if (W_ERROR_IS_OK(expected_result)) { \ + TESTGETCALL(GetPrinter, q) \ + INT_EQUAL(q.out.info->info ## lvl1.devmode->field1, exp_value, field1); \ + q.in.level = lvl2; \ + TESTGETCALL(GetPrinter, q) \ + INT_EQUAL(q.out.info->info ## lvl2.devmode->field2, exp_value, field1); \ + }\ + } while (0) + +#define TEST_DEVMODE_INT_EXP(lvl1, field1, lvl2, field2, value, expected_result) do { \ + TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, value, expected_result); \ + } while (0) + +#define TEST_DEVMODE_INT(lvl1, field1, lvl2, field2, value) do { \ + TEST_DEVMODE_INT_EXP_RESULT(lvl1, field1, lvl2, field2, value, value, WERR_OK); \ + } while (0) + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + ZERO_STRUCT(info8); + + s.in.handle = handle; + s.in.command = 0; + s.in.info_ctr = &info_ctr; + s.in.devmode_ctr = &devmode_ctr; + s.in.secdesc_ctr = &secdesc_ctr; + + q.in.handle = handle; + q.out.info = &info; + +#if 0 + const char *devicename;/* [charset(UTF16)] */ + enum spoolss_DeviceModeSpecVersion specversion; + uint16_t driverversion; + uint16_t __driverextra_length;/* [value(r->driverextra_data.length)] */ + uint32_t fields; +#endif + TEST_DEVMODE_INT_EXP(8, size, 8, size, __LINE__, WERR_INVALID_PARAMETER); + TEST_DEVMODE_INT_EXP(8, size, 8, size, 0, WERR_INVALID_PARAMETER); + TEST_DEVMODE_INT_EXP(8, size, 8, size, 0xffff, WERR_INVALID_PARAMETER); + TEST_DEVMODE_INT_EXP(8, size, 8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0), (devmode_ctr.devmode->__driverextra_length > 0 ) ? WERR_INVALID_PARAMETER : WERR_OK); + TEST_DEVMODE_INT(8, size, 8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0) - devmode_ctr.devmode->__driverextra_length); + + devmode_ctr.devmode->driverextra_data = data_blob_string_const("foobar"); + torture_assert(tctx, + test_devmode_set_level(tctx, b, handle, 8, devmode_ctr.devmode), + "failed to set devmode"); + + TEST_DEVMODE_INT_EXP(8, size, 8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0), (devmode_ctr.devmode->__driverextra_length > 0 ) ? WERR_INVALID_PARAMETER : WERR_OK); + TEST_DEVMODE_INT(8, size, 8, size, ndr_size_spoolss_DeviceMode(devmode_ctr.devmode, 0) - devmode_ctr.devmode->__driverextra_length); + + TEST_DEVMODE_INT(8, orientation, 8, orientation, __LINE__); + TEST_DEVMODE_INT(8, papersize, 8, papersize, __LINE__); + TEST_DEVMODE_INT(8, paperlength, 8, paperlength, __LINE__); + TEST_DEVMODE_INT(8, paperwidth, 8, paperwidth, __LINE__); + TEST_DEVMODE_INT(8, scale, 8, scale, __LINE__); + TEST_DEVMODE_INT(8, copies, 8, copies, __LINE__); + TEST_DEVMODE_INT(8, defaultsource, 8, defaultsource, __LINE__); + TEST_DEVMODE_INT(8, printquality, 8, printquality, __LINE__); + TEST_DEVMODE_INT(8, color, 8, color, __LINE__); + TEST_DEVMODE_INT(8, duplex, 8, duplex, __LINE__); + TEST_DEVMODE_INT(8, yresolution, 8, yresolution, __LINE__); + TEST_DEVMODE_INT(8, ttoption, 8, ttoption, __LINE__); + TEST_DEVMODE_INT(8, collate, 8, collate, __LINE__); +#if 0 + const char *formname;/* [charset(UTF16)] */ +#endif + TEST_DEVMODE_INT(8, logpixels, 8, logpixels, __LINE__); + TEST_DEVMODE_INT(8, bitsperpel, 8, bitsperpel, __LINE__); + TEST_DEVMODE_INT(8, pelswidth, 8, pelswidth, __LINE__); + TEST_DEVMODE_INT(8, pelsheight, 8, pelsheight, __LINE__); + TEST_DEVMODE_INT(8, displayflags, 8, displayflags, __LINE__); + TEST_DEVMODE_INT(8, displayfrequency, 8, displayfrequency, __LINE__); + TEST_DEVMODE_INT(8, icmmethod, 8, icmmethod, __LINE__); + TEST_DEVMODE_INT(8, icmintent, 8, icmintent, __LINE__); + TEST_DEVMODE_INT(8, mediatype, 8, mediatype, __LINE__); + TEST_DEVMODE_INT(8, dithertype, 8, dithertype, __LINE__); + TEST_DEVMODE_INT(8, reserved1, 8, reserved1, __LINE__); + TEST_DEVMODE_INT(8, reserved2, 8, reserved2, __LINE__); + TEST_DEVMODE_INT(8, panningwidth, 8, panningwidth, __LINE__); + TEST_DEVMODE_INT(8, panningheight, 8, panningheight, __LINE__); + + return ret; +} + +static bool call_OpenPrinterEx(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + struct spoolss_DeviceMode *devmode, + struct policy_handle *handle); + +static bool test_PrinterInfo_DevModes(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *name) +{ + union spoolss_PrinterInfo info; + struct spoolss_DeviceMode *devmode; + struct spoolss_DeviceMode *devmode2; + struct policy_handle handle_devmode; + struct dcerpc_binding_handle *b = p->binding_handle; + + /* simply compare level8 and level2 devmode */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 8, &info), ""); + + devmode = info.info8.devmode; + + if (devmode && devmode->size == 0) { + torture_fail(tctx, + "devmode of zero size!"); + } + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + devmode2 = info.info2.devmode; + + if (devmode2 && devmode2->size == 0) { + torture_fail(tctx, + "devmode of zero size!"); + } + + torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2), + "DM level 8 != DM level 2"); + + + /* set devicemode level 8 and see if it persists */ + + devmode->copies = 93; + devmode->formname = talloc_strdup(tctx, "Legal"); + + torture_assert(tctx, test_devmode_set_level(tctx, b, handle, 8, devmode), ""); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 8, &info), ""); + + devmode2 = info.info8.devmode; + + torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2), + "modified DM level 8 != DM level 8 after DM has been set via level 8"); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + devmode2 = info.info2.devmode; + + torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2), + "modified DM level 8 != DM level 2"); + + + /* set devicemode level 2 and see if it persists */ + + devmode->copies = 39; + devmode->formname = talloc_strdup(tctx, "Executive"); + + torture_assert(tctx, test_devmode_set_level(tctx, b, handle, 2, devmode), ""); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 8, &info), ""); + + devmode2 = info.info8.devmode; + + torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2), + "modified DM level 8 != DM level 8 after DM has been set via level 2"); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + devmode2 = info.info2.devmode; + + torture_assert(tctx, test_devicemode_equal(tctx, devmode, devmode2), + "modified DM level 8 != DM level 2"); + + + /* check every single bit in public part of devicemode */ + + torture_assert(tctx, test_devicemode_full(tctx, b, handle), + "failed to set every single devicemode component"); + + + /* change formname upon open and see if it persists in getprinter calls */ + + devmode->formname = talloc_strdup(tctx, "A4"); + devmode->copies = 42; + + torture_assert(tctx, call_OpenPrinterEx(tctx, p, name, devmode, &handle_devmode), + "failed to open printer handle"); + + torture_assert(tctx, test_GetPrinter_level(tctx, b, &handle_devmode, 8, &info), ""); + + devmode2 = info.info8.devmode; + + if (strequal(devmode->devicename, devmode2->devicename)) { + torture_warning(tctx, "devicenames are the same\n"); + } else { + torture_comment(tctx, "devicename passed in for open: %s\n", devmode->devicename); + torture_comment(tctx, "devicename after level 8 get: %s\n", devmode2->devicename); + } + + if (strequal(devmode->formname, devmode2->formname)) { + torture_warning(tctx, "formname are the same\n"); + } else { + torture_comment(tctx, "formname passed in for open: %s\n", devmode->formname); + torture_comment(tctx, "formname after level 8 get: %s\n", devmode2->formname); + } + + if (devmode->copies == devmode2->copies) { + torture_warning(tctx, "copies are the same\n"); + } else { + torture_comment(tctx, "copies passed in for open: %d\n", devmode->copies); + torture_comment(tctx, "copies after level 8 get: %d\n", devmode2->copies); + } + + torture_assert(tctx, test_GetPrinter_level(tctx, b, &handle_devmode, 2, &info), ""); + + devmode2 = info.info2.devmode; + + if (strequal(devmode->devicename, devmode2->devicename)) { + torture_warning(tctx, "devicenames are the same\n"); + } else { + torture_comment(tctx, "devicename passed in for open: %s\n", devmode->devicename); + torture_comment(tctx, "devicename after level 2 get: %s\n", devmode2->devicename); + } + + if (strequal(devmode->formname, devmode2->formname)) { + torture_warning(tctx, "formname is the same\n"); + } else { + torture_comment(tctx, "formname passed in for open: %s\n", devmode->formname); + torture_comment(tctx, "formname after level 2 get: %s\n", devmode2->formname); + } + + if (devmode->copies == devmode2->copies) { + torture_warning(tctx, "copies are the same\n"); + } else { + torture_comment(tctx, "copies passed in for open: %d\n", devmode->copies); + torture_comment(tctx, "copies after level 2 get: %d\n", devmode2->copies); + } + + test_ClosePrinter(tctx, b, &handle_devmode); + + return true; +} + +/* + * wrapper call that saves original devmode, runs tests, and restores devmode + */ + +static bool test_PrinterInfo_DevMode(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *name, + struct spoolss_DeviceMode *addprinter_devmode) +{ + union spoolss_PrinterInfo info; + struct spoolss_DeviceMode *devmode; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing Printer Devicemodes\n"); + + /* save original devmode */ + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 8, &info), + "failed to get initial global devicemode"); + + devmode = info.info8.devmode; + + if (devmode && devmode->size == 0) { + torture_fail(tctx, + "devmode of zero size!"); + } + + if (addprinter_devmode) { + if (!test_devicemode_equal(tctx, devmode, addprinter_devmode)) { + torture_warning(tctx, "current global DM is != DM provided in addprinter"); + } + } + + /* run tests */ + + ret = test_PrinterInfo_DevModes(tctx, p, handle, name); + + /* restore original devmode */ + + torture_assert(tctx, test_devmode_set_level(tctx, b, handle, 8, devmode), + "failed to restore initial global device mode"); + + torture_comment(tctx, "Printer Devicemodes test %s\n\n", + ret ? "succeeded" : "failed"); + + + return ret; +} + +bool test_ClosePrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_ClosePrinter r; + + r.in.handle = handle; + r.out.handle = handle; + + torture_comment(tctx, "Testing ClosePrinter\n"); + + status = dcerpc_spoolss_ClosePrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ClosePrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, "ClosePrinter failed"); + + return true; +} + +static bool test_GetForm_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *form_name, + uint32_t level, + union spoolss_FormInfo *info_p) +{ + NTSTATUS status; + struct spoolss_GetForm r; + uint32_t needed; + + r.in.handle = handle; + r.in.form_name = form_name; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_comment(tctx, "Testing GetForm(%s) level %d\n", form_name, r.in.level); + + status = dcerpc_spoolss_GetForm_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetForm failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + status = dcerpc_spoolss_GetForm_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetForm failed"); + + torture_assert_werr_ok(tctx, r.out.result, "GetForm failed"); + + torture_assert(tctx, r.out.info, "No form info returned"); + } + + torture_assert_werr_ok(tctx, r.out.result, "GetForm failed"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_FormInfo, r.out.info, r.in.level, needed, 4); + + if (info_p) { + *info_p = *r.out.info; + } + + return true; +} + +static bool test_GetForm(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *form_name, + uint32_t level) +{ + return test_GetForm_args(tctx, b, handle, form_name, level, NULL); +} + +static bool test_EnumForms(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server, + uint32_t level, + uint32_t *count_p, + union spoolss_FormInfo **info_p) +{ + struct spoolss_EnumForms r; + uint32_t needed; + uint32_t count; + union spoolss_FormInfo *info; + + r.in.handle = handle; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumForms level %d\n", r.in.level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumForms_r(b, tctx, &r), + "EnumForms failed"); + + if ((r.in.level == 2) && (W_ERROR_EQUAL(r.out.result, WERR_INVALID_LEVEL))) { + torture_skip(tctx, "EnumForms level 2 not supported"); + } + + if (print_server && W_ERROR_EQUAL(r.out.result, WERR_INVALID_HANDLE)) { + torture_fail(tctx, "EnumForms on the PrintServer isn't supported by test server (NT4)"); + } + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumForms_r(b, tctx, &r), + "EnumForms failed"); + + torture_assert(tctx, info, "No forms returned"); + } + + torture_assert_werr_ok(tctx, r.out.result, "EnumForms failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumForms, info, r.in.level, count, needed, 4); + + if (info_p) { + *info_p = info; + } + if (count_p) { + *count_p = count; + } + + return true; +} + +static bool test_EnumForms_all(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server) +{ + uint32_t levels[] = { 1, 2 }; + int i, j; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + uint32_t count = 0; + union spoolss_FormInfo *info = NULL; + + torture_assert(tctx, + test_EnumForms(tctx, b, handle, print_server, levels[i], &count, &info), + "failed to enum forms"); + + for (j = 0; j < count; j++) { + if (!print_server) { + torture_assert(tctx, + test_GetForm(tctx, b, handle, info[j].info1.form_name, levels[i]), + "failed to get form"); + } + } + } + + return true; +} + +static bool test_EnumForms_find_one(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server, + const char *form_name) +{ + union spoolss_FormInfo *info = NULL; + uint32_t count = 0; + bool found = false; + int i; + + torture_assert(tctx, + test_EnumForms(tctx, b, handle, print_server, 1, &count, &info), + "failed to enumerate forms"); + + for (i=0; i<count; i++) { + if (strequal(form_name, info[i].info1.form_name)) { + found = true; + break; + } + } + + return found; +} + +static bool test_DeleteForm(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *form_name, + WERROR expected_result) +{ + struct spoolss_DeleteForm r; + + r.in.handle = handle; + r.in.form_name = form_name; + + torture_comment(tctx, "Testing DeleteForm(%s)\n", form_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeleteForm_r(b, tctx, &r), + "DeleteForm failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "DeleteForm gave unexpected result"); + if (W_ERROR_IS_OK(r.out.result)) { + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeleteForm_r(b, tctx, &r), + "2nd DeleteForm failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_FORM_NAME, + "2nd DeleteForm failed"); + } + + return true; +} + +static bool test_AddForm(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + union spoolss_AddFormInfo *info, + WERROR expected_result) +{ + struct spoolss_AddForm r; + struct spoolss_AddFormInfoCtr info_ctr; + + info_ctr.level = level; + info_ctr.info = *info; + + if (level != 1) { + torture_skip(tctx, "only level 1 supported"); + } + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + + torture_comment(tctx, "Testing AddForm(%s) level %d, type %d\n", + r.in.info_ctr->info.info1->form_name, level, + r.in.info_ctr->info.info1->flags); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_AddForm_r(b, tctx, &r), + "AddForm failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "AddForm gave unexpected result"); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_AddForm_r(b, tctx, &r), + "2nd AddForm failed"); + if (W_ERROR_EQUAL(expected_result, WERR_INVALID_PARAMETER)) { + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "2nd AddForm gave unexpected result"); + } else { + torture_assert_werr_equal(tctx, r.out.result, WERR_FILE_EXISTS, + "2nd AddForm gave unexpected result"); + } + + return true; +} + +static bool test_SetForm(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *form_name, + uint32_t level, + union spoolss_AddFormInfo *info) +{ + struct spoolss_SetForm r; + struct spoolss_AddFormInfoCtr info_ctr; + + info_ctr.level = level; + info_ctr.info = *info; + + r.in.handle = handle; + r.in.form_name = form_name; + r.in.info_ctr = &info_ctr; + + torture_comment(tctx, "Testing SetForm(%s) level %d\n", + form_name, level); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetForm_r(b, tctx, &r), + "SetForm failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "SetForm failed"); + + return true; +} + +static bool test_GetForm_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char *form_name, + enum winreg_Type *w_type, + uint32_t *w_size, + uint32_t *w_length, + uint8_t **w_data); + +static bool test_Forms_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server, + const char *printer_name, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle, + const char *form_name, + struct spoolss_AddFormInfo1 *info1, + WERROR expected_add_result, + WERROR expected_delete_result) +{ + union spoolss_FormInfo info; + union spoolss_AddFormInfo add_info; + + enum winreg_Type w_type; + uint32_t w_size; + uint32_t w_length; + uint8_t *w_data; + + add_info.info1 = info1; + + torture_assert(tctx, + test_AddForm(tctx, b, handle, 1, &add_info, expected_add_result), + "failed to add form"); + + if (winreg_handle && hive_handle && W_ERROR_IS_OK(expected_add_result)) { + + struct spoolss_FormInfo1 i1; + + torture_assert(tctx, + test_GetForm_winreg(tctx, winreg_handle, hive_handle, TOP_LEVEL_CONTROL_FORMS_KEY, form_name, &w_type, &w_size, &w_length, &w_data), + "failed to get form via winreg"); + + i1.size.width = IVAL(w_data, 0); + i1.size.height = IVAL(w_data, 4); + i1.area.left = IVAL(w_data, 8); + i1.area.top = IVAL(w_data, 12); + i1.area.right = IVAL(w_data, 16); + i1.area.bottom = IVAL(w_data, 20); + /* skip index here */ + i1.flags = IVAL(w_data, 28); + + torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type"); + torture_assert_int_equal(tctx, w_size, 0x20, "unexpected size"); + torture_assert_int_equal(tctx, w_length, 0x20, "unexpected length"); + torture_assert_int_equal(tctx, i1.size.width, add_info.info1->size.width, "width mismatch"); + torture_assert_int_equal(tctx, i1.size.height, add_info.info1->size.height, "height mismatch"); + torture_assert_int_equal(tctx, i1.area.left, add_info.info1->area.left, "left mismatch"); + torture_assert_int_equal(tctx, i1.area.top, add_info.info1->area.top, "top mismatch"); + torture_assert_int_equal(tctx, i1.area.right, add_info.info1->area.right, "right mismatch"); + torture_assert_int_equal(tctx, i1.area.bottom, add_info.info1->area.bottom, "bottom mismatch"); + torture_assert_int_equal(tctx, i1.flags, add_info.info1->flags, "flags mismatch"); + } + + if (!print_server && W_ERROR_IS_OK(expected_add_result)) { + torture_assert(tctx, + test_GetForm_args(tctx, b, handle, form_name, 1, &info), + "failed to get added form"); + + torture_assert_int_equal(tctx, info.info1.size.width, add_info.info1->size.width, "width mismatch"); + torture_assert_int_equal(tctx, info.info1.size.height, add_info.info1->size.height, "height mismatch"); + torture_assert_int_equal(tctx, info.info1.area.left, add_info.info1->area.left, "left mismatch"); + torture_assert_int_equal(tctx, info.info1.area.top, add_info.info1->area.top, "top mismatch"); + torture_assert_int_equal(tctx, info.info1.area.right, add_info.info1->area.right, "right mismatch"); + torture_assert_int_equal(tctx, info.info1.area.bottom, add_info.info1->area.bottom, "bottom mismatch"); + torture_assert_int_equal(tctx, info.info1.flags, add_info.info1->flags, "flags mismatch"); + + if (winreg_handle && hive_handle) { + + struct spoolss_FormInfo1 i1; + + i1.size.width = IVAL(w_data, 0); + i1.size.height = IVAL(w_data, 4); + i1.area.left = IVAL(w_data, 8); + i1.area.top = IVAL(w_data, 12); + i1.area.right = IVAL(w_data, 16); + i1.area.bottom = IVAL(w_data, 20); + /* skip index here */ + i1.flags = IVAL(w_data, 28); + + torture_assert_int_equal(tctx, i1.size.width, info.info1.size.width, "width mismatch"); + torture_assert_int_equal(tctx, i1.size.height, info.info1.size.height, "height mismatch"); + torture_assert_int_equal(tctx, i1.area.left, info.info1.area.left, "left mismatch"); + torture_assert_int_equal(tctx, i1.area.top, info.info1.area.top, "top mismatch"); + torture_assert_int_equal(tctx, i1.area.right, info.info1.area.right, "right mismatch"); + torture_assert_int_equal(tctx, i1.area.bottom, info.info1.area.bottom, "bottom mismatch"); + torture_assert_int_equal(tctx, i1.flags, info.info1.flags, "flags mismatch"); + } + + add_info.info1->size.width = 1234; + + torture_assert(tctx, + test_SetForm(tctx, b, handle, form_name, 1, &add_info), + "failed to set form"); + torture_assert(tctx, + test_GetForm_args(tctx, b, handle, form_name, 1, &info), + "failed to get set form"); + + torture_assert_int_equal(tctx, info.info1.size.width, add_info.info1->size.width, "width mismatch"); + } + + if (!W_ERROR_EQUAL(expected_add_result, WERR_INVALID_PARAMETER)) { + torture_assert(tctx, + test_EnumForms_find_one(tctx, b, handle, print_server, form_name), + "Newly added form not found in enum call"); + } + + torture_assert(tctx, + test_DeleteForm(tctx, b, handle, form_name, expected_delete_result), + "failed to delete form"); + + return true; +} + +static bool test_Forms(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server, + const char *printer_name, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + const struct spoolss_FormSize size = { + .width = 50, + .height = 25 + }; + const struct spoolss_FormArea area = { + .left = 5, + .top = 10, + .right = 45, + .bottom = 15 + }; + int i; + + struct { + struct spoolss_AddFormInfo1 info1; + WERROR expected_add_result; + WERROR expected_delete_result; + } forms[] = { + { + .info1 = { + .flags = SPOOLSS_FORM_USER, + .form_name = "testform_user", + .size = size, + .area = area, + }, + .expected_add_result = WERR_OK, + .expected_delete_result = WERR_OK + }, +/* + weird, we can add a builtin form but we can never remove it + again - gd + + { + .info1 = { + .flags = SPOOLSS_FORM_BUILTIN, + .form_name = "testform_builtin", + .size = size, + .area = area, + }, + .expected_add_result = WERR_OK, + .expected_delete_result = WERR_INVALID_PARAMETER, + }, +*/ + { + .info1 = { + .flags = SPOOLSS_FORM_PRINTER, + .form_name = "testform_printer", + .size = size, + .area = area, + }, + .expected_add_result = WERR_OK, + .expected_delete_result = WERR_OK + }, + { + .info1 = { + .flags = SPOOLSS_FORM_USER, + .form_name = "Letter", + .size = size, + .area = area, + }, + .expected_add_result = WERR_FILE_EXISTS, + .expected_delete_result = WERR_INVALID_PARAMETER + }, + { + .info1 = { + .flags = SPOOLSS_FORM_BUILTIN, + .form_name = "Letter", + .size = size, + .area = area, + }, + .expected_add_result = WERR_FILE_EXISTS, + .expected_delete_result = WERR_INVALID_PARAMETER + }, + { + .info1 = { + .flags = SPOOLSS_FORM_PRINTER, + .form_name = "Letter", + .size = size, + .area = area, + }, + .expected_add_result = WERR_FILE_EXISTS, + .expected_delete_result = WERR_INVALID_PARAMETER + }, + { + .info1 = { + .flags = 12345, + .form_name = "invalid_flags", + .size = size, + .area = area, + }, + .expected_add_result = WERR_INVALID_PARAMETER, + .expected_delete_result = WERR_INVALID_FORM_NAME + } + + }; + + for (i=0; i < ARRAY_SIZE(forms); i++) { + torture_assert(tctx, + test_Forms_args(tctx, b, handle, print_server, printer_name, + winreg_handle, hive_handle, + forms[i].info1.form_name, + &forms[i].info1, + forms[i].expected_add_result, + forms[i].expected_delete_result), + talloc_asprintf(tctx, "failed to test form '%s'", forms[i].info1.form_name)); + } + + return true; +} + +static bool test_EnumPorts_old(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + NTSTATUS status; + struct spoolss_EnumPorts r; + uint32_t needed; + uint32_t count; + union spoolss_PortInfo *info; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.servername = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.level = 2; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPorts\n"); + + status = dcerpc_spoolss_EnumPorts_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "EnumPorts failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_EnumPorts_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "EnumPorts failed"); + torture_assert_werr_ok(tctx, r.out.result, "EnumPorts failed"); + + torture_assert(tctx, info, "No ports returned"); + } + + torture_assert_werr_ok(tctx, r.out.result, "EnumPorts failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPorts, info, 2, count, needed, 4); + + return true; +} + +static bool test_AddPort(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + NTSTATUS status; + struct spoolss_AddPort r; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + r.in.unknown = 0; + r.in.monitor_name = "foo"; + + torture_comment(tctx, "Testing AddPort\n"); + + status = dcerpc_spoolss_AddPort_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "AddPort failed"); + + /* win2k3 returns WERR_NOT_SUPPORTED */ + +#if 0 + + if (!W_ERROR_IS_OK(r.out.result)) { + printf("AddPort failed - %s\n", win_errstr(r.out.result)); + return false; + } + +#endif + + return true; +} + +static bool test_GetJob_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id, + uint32_t level, + union spoolss_JobInfo *info_p) +{ + NTSTATUS status; + struct spoolss_GetJob r; + union spoolss_JobInfo info; + uint32_t needed; + + r.in.handle = handle; + r.in.job_id = job_id; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.info = &info; + + torture_comment(tctx, "Testing GetJob(%d), level %d\n", job_id, r.in.level); + + status = dcerpc_spoolss_GetJob_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetJob failed"); + if (level == 0) { + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, "Unexpected return code"); + } + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_GetJob_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetJob failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, "GetJob failed"); + torture_assert(tctx, r.out.info, "No job info returned"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_JobInfo, r.out.info, r.in.level, needed, 4); + + if (info_p) { + *info_p = *r.out.info; + } + + return true; +} + +#if 0 +static bool test_GetJob(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id) +{ + uint32_t levels[] = {0, 1, 2 /* 3, 4 */}; + uint32_t i; + + for (i=0; i < ARRAY_SIZE(levels); i++) { + torture_assert(tctx, + test_GetJob_args(tctx, b, handle, job_id, levels[i], NULL), + "GetJob failed"); + } + + return true; +} +#endif + +static bool test_SetJob(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id, + struct spoolss_JobInfoContainer *ctr, + enum spoolss_JobControl command) +{ + NTSTATUS status; + struct spoolss_SetJob r; + + r.in.handle = handle; + r.in.job_id = job_id; + r.in.ctr = ctr; + r.in.command = command; + + switch (command) { + case SPOOLSS_JOB_CONTROL_PAUSE: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_PAUSE\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_RESUME: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_RESUME\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_CANCEL: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_CANCEL\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_RESTART: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_RESTART\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_DELETE: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_DELETE\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_SEND_TO_PRINTER: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_SEND_TO_PRINTER\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_LAST_PAGE_EJECTED: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_LAST_PAGE_EJECTED\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_RETAIN: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_RETAIN\n", job_id); + break; + case SPOOLSS_JOB_CONTROL_RELEASE: + torture_comment(tctx, "Testing SetJob(%d), SPOOLSS_JOB_CONTROL_RELEASE\n", job_id); + break; + default: + torture_comment(tctx, "Testing SetJob(%d)\n", job_id); + break; + } + + status = dcerpc_spoolss_SetJob_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "SetJob failed"); + torture_assert_werr_ok(tctx, r.out.result, "SetJob failed"); + + return true; +} + +static bool test_AddJob(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_AddJob r; + uint32_t needed; + + r.in.level = 0; + r.in.handle = handle; + r.in.offered = 0; + r.out.needed = &needed; + r.in.buffer = r.out.buffer = NULL; + + torture_comment(tctx, "Testing AddJob\n"); + + status = dcerpc_spoolss_AddJob_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "AddJob failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, "AddJob failed"); + + r.in.level = 1; + + status = dcerpc_spoolss_AddJob_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "AddJob failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, "AddJob failed"); + + return true; +} + + +static bool test_EnumJobs_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t level, + WERROR werr_expected, + uint32_t *count_p, + union spoolss_JobInfo **info_p) +{ + NTSTATUS status; + struct spoolss_EnumJobs r; + uint32_t needed; + uint32_t count; + union spoolss_JobInfo *info; + + r.in.handle = handle; + r.in.firstjob = 0; + r.in.numjobs = 0xffffffff; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumJobs level %d\n", level); + + status = dcerpc_spoolss_EnumJobs_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "EnumJobs failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + status = dcerpc_spoolss_EnumJobs_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "EnumJobs failed"); + torture_assert_werr_equal(tctx, r.out.result, werr_expected, + "EnumJobs failed"); + torture_assert(tctx, info, "No jobs returned"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumJobs, *r.out.info, r.in.level, count, needed, 4); + + } else { + torture_assert_werr_equal(tctx, r.out.result, werr_expected, + "EnumJobs failed"); + } + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + return true; +} + +static bool test_JobPropertiesEnum(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id) +{ + struct spoolss_EnumJobNamedProperties r; + uint32_t pcProperties = 0; + struct spoolss_PrintNamedProperty *ppProperties = NULL; + + r.in.hPrinter = handle; + r.in.JobId = job_id; + r.out.pcProperties = &pcProperties; + r.out.ppProperties = &ppProperties; + + torture_comment(tctx, "Testing EnumJobNamedProperties(%d)\n", job_id); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumJobNamedProperties_r(b, tctx, &r), + "spoolss_EnumJobNamedProperties failed"); + torture_assert_werr_ok(tctx, r.out.result, + "spoolss_EnumJobNamedProperties failed"); + + return true; +} + +static bool test_JobPropertySet(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id, + struct spoolss_PrintNamedProperty *property) +{ + struct spoolss_SetJobNamedProperty r; + + r.in.hPrinter = handle; + r.in.JobId = job_id; + r.in.pProperty = property; + + torture_comment(tctx, "Testing SetJobNamedProperty(%d) %s - %d\n", + job_id, property->propertyName, + property->propertyValue.ePropertyType); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_SetJobNamedProperty_r(b, tctx, &r), + "spoolss_SetJobNamedProperty failed"); + torture_assert_werr_ok(tctx, r.out.result, + "spoolss_SetJobNamedProperty failed"); + + return true; +} + +static bool test_JobPropertyGetValue(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id, + const char *property_name, + struct spoolss_PrintPropertyValue *value) +{ + struct spoolss_GetJobNamedPropertyValue r; + + r.in.hPrinter = handle; + r.in.JobId = job_id; + r.in.pszName = property_name; + r.out.pValue = value; + + torture_comment(tctx, "Testing GetJobNamedPropertyValue(%d) %s\n", + job_id, property_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetJobNamedPropertyValue_r(b, tctx, &r), + "spoolss_GetJobNamedPropertyValue failed"); + torture_assert_werr_ok(tctx, r.out.result, + "spoolss_GetJobNamedPropertyValue failed"); + + return true; +} + +static bool test_JobPropertyDelete(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id, + const char *property_name) +{ + struct spoolss_DeleteJobNamedProperty r; + + r.in.hPrinter = handle; + r.in.JobId = job_id; + r.in.pszName = property_name; + + torture_comment(tctx, "Testing DeleteJobNamedProperty(%d) %s\n", + job_id, property_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeleteJobNamedProperty_r(b, tctx, &r), + "spoolss_DeleteJobNamedProperty failed"); + torture_assert_werr_ok(tctx, r.out.result, + "spoolss_DeleteJobNamedProperty failed"); + + return true; +} + +static bool test_DoPrintTest_add_one_job_common(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *document_name, + const char *datatype, + uint32_t *job_id) +{ + NTSTATUS status; + struct spoolss_StartDocPrinter s; + struct spoolss_DocumentInfoCtr info_ctr; + struct spoolss_DocumentInfo1 info1; + struct spoolss_StartPagePrinter sp; + struct spoolss_WritePrinter w; + struct spoolss_EndPagePrinter ep; + struct spoolss_EndDocPrinter e; + int i; + uint32_t num_written; + + torture_comment(tctx, "Testing StartDocPrinter\n"); + + s.in.handle = handle; + s.in.info_ctr = &info_ctr; + s.out.job_id = job_id; + + info1.document_name = document_name; + info1.output_file = NULL; + info1.datatype = datatype; + + info_ctr.level = 1; + info_ctr.info.info1 = &info1; + + status = dcerpc_spoolss_StartDocPrinter_r(b, tctx, &s); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_StartDocPrinter failed"); + torture_assert_werr_ok(tctx, s.out.result, "StartDocPrinter failed"); + + for (i=1; i < 4; i++) { + union spoolss_JobInfo ginfo; + bool ok; + + torture_comment(tctx, "Testing StartPagePrinter: Page[%d], JobId[%d]\n", i, *job_id); + + sp.in.handle = handle; + + status = dcerpc_spoolss_StartPagePrinter_r(b, tctx, &sp); + torture_assert_ntstatus_ok(tctx, status, + "dcerpc_spoolss_StartPagePrinter failed"); + torture_assert_werr_ok(tctx, sp.out.result, "StartPagePrinter failed"); + + ok = test_GetJob_args(tctx, b, handle, *job_id, 1, &ginfo); + if (!ok) { + torture_comment(tctx, "test_GetJob failed for JobId[%d]\n", *job_id); + } + + torture_comment(tctx, "Testing WritePrinter: Page[%d], JobId[%d]\n", i, *job_id); + + w.in.handle = handle; + w.in.data = data_blob_string_const(talloc_asprintf(tctx,"TortureTestPage: %d\nData\n",i)); + w.out.num_written = &num_written; + + status = dcerpc_spoolss_WritePrinter_r(b, tctx, &w); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_WritePrinter failed"); + torture_assert_werr_ok(tctx, w.out.result, "WritePrinter failed"); + + torture_comment(tctx, "Testing EndPagePrinter: Page[%d], JobId[%d]\n", i, *job_id); + + ep.in.handle = handle; + + status = dcerpc_spoolss_EndPagePrinter_r(b, tctx, &ep); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EndPagePrinter failed"); + torture_assert_werr_ok(tctx, ep.out.result, "EndPagePrinter failed"); + } + + torture_comment(tctx, "Testing EndDocPrinter: JobId[%d]\n", *job_id); + + e.in.handle = handle; + + status = dcerpc_spoolss_EndDocPrinter_r(b, tctx, &e); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_spoolss_EndDocPrinter failed"); + torture_assert_werr_ok(tctx, e.out.result, "EndDocPrinter failed"); + + return true; +} + +static bool test_DoPrintTest_add_one_job(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *document_name, + uint32_t *job_id) +{ + test_DoPrintTest_add_one_job_common(tctx, b, handle, document_name, "RAW", job_id); + + return true; +} + +static bool test_DoPrintTest_add_one_job_v4(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *document_name, + uint32_t *job_id) +{ + test_DoPrintTest_add_one_job_common(tctx, b, handle, document_name, "XPS_PASS", job_id); + + return true; +} + + +static bool test_DoPrintTest_check_jobs(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t num_jobs, + uint32_t *job_ids) +{ + uint32_t count; + union spoolss_JobInfo *info = NULL; + int i; + + torture_assert(tctx, + test_AddJob(tctx, b, handle), + "AddJob failed"); + + torture_assert(tctx, + test_EnumJobs_args(tctx, b, handle, 1, WERR_OK, &count, &info), + "EnumJobs level 1 failed"); + + torture_assert_int_equal(tctx, count, num_jobs, "unexpected number of jobs in queue"); + + for (i=0; i < num_jobs; i++) { + union spoolss_JobInfo ginfo; + const char *document_name; + const char *new_document_name = "any_other_docname"; + struct spoolss_JobInfoContainer ctr; + struct spoolss_SetJobInfo1 info1; + + torture_assert_int_equal(tctx, info[i].info1.job_id, job_ids[i], "job id mismatch"); + + torture_assert(tctx, + test_GetJob_args(tctx, b, handle, info[i].info1.job_id, 1, &ginfo), + "failed to call test_GetJob"); + + torture_assert_int_equal(tctx, ginfo.info1.job_id, info[i].info1.job_id, "job id mismatch"); + + document_name = ginfo.info1.document_name; + + info1.job_id = ginfo.info1.job_id; + info1.printer_name = ginfo.info1.printer_name; + info1.server_name = ginfo.info1.server_name; + info1.user_name = ginfo.info1.user_name; + info1.document_name = new_document_name; + info1.data_type = ginfo.info1.data_type; + info1.text_status = ginfo.info1.text_status; + info1.status = ginfo.info1.status; + info1.priority = ginfo.info1.priority; + info1.position = ginfo.info1.position; + info1.total_pages = ginfo.info1.total_pages; + info1.pages_printed = ginfo.info1.pages_printed; + info1.submitted = ginfo.info1.submitted; + + ctr.level = 1; + ctr.info.info1 = &info1; + + torture_assert(tctx, + test_SetJob(tctx, b, handle, info[i].info1.job_id, &ctr, 0), + "failed to call test_SetJob level 1"); + + torture_assert(tctx, + test_GetJob_args(tctx, b, handle, info[i].info1.job_id, 1, &ginfo), + "failed to call test_GetJob"); + + if (strequal(ginfo.info1.document_name, document_name)) { + torture_warning(tctx, + "document_name did *NOT* change from '%s' to '%s'\n", + document_name, new_document_name); + } + } + + for (i=0; i < num_jobs; i++) { + if (!test_SetJob(tctx, b, handle, info[i].info1.job_id, NULL, SPOOLSS_JOB_CONTROL_PAUSE)) { + torture_warning(tctx, "failed to pause printjob\n"); + } + if (!test_SetJob(tctx, b, handle, info[i].info1.job_id, NULL, SPOOLSS_JOB_CONTROL_RESUME)) { + torture_warning(tctx, "failed to resume printjob\n"); + } + } + + return true; +} + +static bool test_DoPrintTest(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + bool ret = true; + uint32_t num_jobs = 8; + uint32_t *job_ids; + int i; + + torture_comment(tctx, "Testing real print operations\n"); + + job_ids = talloc_zero_array(tctx, uint32_t, num_jobs); + + for (i=0; i < num_jobs; i++) { + ret &= test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]); + } + + for (i=0; i < num_jobs; i++) { + ret &= test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE); + } + + for (i=0; i < num_jobs; i++) { + ret &= test_DoPrintTest_add_one_job_v4(tctx, b, handle, "TorturePrintJob v4", &job_ids[i]); + } + + for (i=0; i < num_jobs; i++) { + ret &= test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE); + } + + if (ret == true) { + torture_comment(tctx, "real print operations test succeeded\n\n"); + } + + return ret; +} + +static bool test_DoPrintTest_extended(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + bool ret = true; + uint32_t num_jobs = 8; + uint32_t *job_ids; + int i; + torture_comment(tctx, "Testing real print operations (extended)\n"); + + job_ids = talloc_zero_array(tctx, uint32_t, num_jobs); + + for (i=0; i < num_jobs; i++) { + ret &= test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]); + } + + ret &= test_DoPrintTest_check_jobs(tctx, b, handle, num_jobs, job_ids); + + for (i=0; i < num_jobs; i++) { + ret &= test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE); + } + + if (ret == true) { + torture_comment(tctx, "real print operations (extended) test succeeded\n\n"); + } + + return ret; +} + +static bool test_JobPrintProperties_equal(struct torture_context *tctx, + struct spoolss_PrintPropertyValue *got, + struct spoolss_PrintNamedProperty *exp) +{ + torture_assert_int_equal(tctx, + got->ePropertyType, + exp->propertyValue.ePropertyType, + "ePropertyType"); + + switch (exp->propertyValue.ePropertyType) { + case kRpcPropertyTypeString: + torture_assert_str_equal(tctx, + got->value.propertyString, + exp->propertyValue.value.propertyString, + "propertyString"); + break; + case kRpcPropertyTypeInt32: + torture_assert_int_equal(tctx, + got->value.propertyInt32, + exp->propertyValue.value.propertyInt32, + "propertyInt32"); + break; + case kRpcPropertyTypeInt64: + torture_assert_u64_equal(tctx, + got->value.propertyInt64, + exp->propertyValue.value.propertyInt64, + "propertyInt64"); + break; + case kRpcPropertyTypeByte: + torture_assert_int_equal(tctx, + got->value.propertyByte, + exp->propertyValue.value.propertyByte, + "propertyByte"); + break; + case kRpcPropertyTypeBuffer: + torture_assert_int_equal(tctx, + got->value.propertyBlob.cbBuf, + exp->propertyValue.value.propertyBlob.cbBuf, + "propertyBlob.cbBuf"); + torture_assert_mem_equal(tctx, + got->value.propertyBlob.pBuf, + exp->propertyValue.value.propertyBlob.pBuf, + exp->propertyValue.value.propertyBlob.cbBuf, + "propertyBlob.pBuf"); + + break; + + } + + return true; +} + +static bool test_JobPrintProperties(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t job_id) +{ + struct spoolss_PrintNamedProperty in; + struct spoolss_PrintPropertyValue out; + int i; + DATA_BLOB blob = data_blob_string_const("blob"); + struct { + const char *property_name; + enum spoolss_EPrintPropertyType type; + union spoolss_PrintPropertyValueUnion value; + WERROR expected_result; + } tests[] = { + { + .property_name = "torture_property_string", + .type = kRpcPropertyTypeString, + .value.propertyString = "torture_property_value_string", + },{ + .property_name = "torture_property_int32", + .type = kRpcPropertyTypeInt32, + .value.propertyInt32 = 42, + },{ + .property_name = "torture_property_int64", + .type = kRpcPropertyTypeInt64, + .value.propertyInt64 = 0xaffe, + },{ + .property_name = "torture_property_byte", + .type = kRpcPropertyTypeByte, + .value.propertyByte = 0xab, + },{ + .property_name = "torture_property_buffer", + .type = kRpcPropertyTypeBuffer, + .value.propertyBlob.cbBuf = blob.length, + .value.propertyBlob.pBuf = blob.data, + } + }; + + torture_assert(tctx, + test_JobPropertiesEnum(tctx, b, handle, job_id), + "failed to enum properties"); + + for (i=0; i <ARRAY_SIZE(tests); i++) { + + in.propertyName = tests[i].property_name; + in.propertyValue.ePropertyType = tests[i].type; + in.propertyValue.value = tests[i].value; + + torture_assert(tctx, + test_JobPropertySet(tctx, b, handle, job_id, &in), + "failed to set property"); + + torture_assert(tctx, + test_JobPropertyGetValue(tctx, b, handle, job_id, in.propertyName, &out), + "failed to get property"); + + torture_assert(tctx, + test_JobPrintProperties_equal(tctx, &out, &in), + "property unequal"); + + torture_assert(tctx, + test_JobPropertiesEnum(tctx, b, handle, job_id), + "failed to enum properties"); + + torture_assert(tctx, + test_JobPropertyDelete(tctx, b, handle, job_id, in.propertyName), + "failed to delete job property"); + } + + torture_assert(tctx, + test_JobPropertiesEnum(tctx, b, handle, job_id), + "failed to enum properties"); + + return true; +} + +static bool test_DoPrintTest_properties(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + uint32_t num_jobs = 8; + uint32_t *job_ids; + int i; + torture_comment(tctx, "Testing real print operations (properties)\n"); + + job_ids = talloc_zero_array(tctx, uint32_t, num_jobs); + + for (i=0; i < num_jobs; i++) { + torture_assert(tctx, + test_DoPrintTest_add_one_job(tctx, b, handle, "TorturePrintJob", &job_ids[i]), + "failed to create print job"); + } + + for (i=0; i < num_jobs; i++) { + torture_assert(tctx, + test_JobPrintProperties(tctx, b, handle, job_ids[i]), + "failed to test job properties"); + } + + + for (i=0; i < num_jobs; i++) { + torture_assert(tctx, + test_SetJob(tctx, b, handle, job_ids[i], NULL, SPOOLSS_JOB_CONTROL_DELETE), + "failed to delete printjob"); + } + + torture_comment(tctx, "real print operations (properties) test succeeded\n\n"); + + return true; +} + +static bool test_PausePrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_SetPrinter r; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info_ctr.level = 0; + info_ctr.info.info0 = NULL; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.in.command = SPOOLSS_PRINTER_CONTROL_PAUSE; + + torture_comment(tctx, "Testing SetPrinter: SPOOLSS_PRINTER_CONTROL_PAUSE\n"); + + status = dcerpc_spoolss_SetPrinter_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "SetPrinter failed"); + + torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed"); + + return true; +} + +static bool test_ResumePrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_SetPrinter r; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info_ctr.level = 0; + info_ctr.info.info0 = NULL; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.in.command = SPOOLSS_PRINTER_CONTROL_RESUME; + + torture_comment(tctx, "Testing SetPrinter: SPOOLSS_PRINTER_CONTROL_RESUME\n"); + + status = dcerpc_spoolss_SetPrinter_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "SetPrinter failed"); + + torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed"); + + return true; +} + +static bool test_printer_purge(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_SetPrinter r; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info_ctr.level = 0; + info_ctr.info.info0 = NULL; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.in.command = SPOOLSS_PRINTER_CONTROL_PURGE; + + torture_comment(tctx, "Testing SetPrinter: SPOOLSS_PRINTER_CONTROL_PURGE\n"); + + status = dcerpc_spoolss_SetPrinter_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "SetPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed"); + + return true; +} + +static bool test_GetPrinterData_checktype(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *expected_type, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + NTSTATUS status; + struct spoolss_GetPrinterData r; + uint32_t needed; + enum winreg_Type type; + union spoolss_PrinterData data; + + r.in.handle = handle; + r.in.value_name = value_name; + r.in.offered = 0; + r.out.needed = &needed; + r.out.type = &type; + r.out.data = talloc_zero_array(tctx, uint8_t, r.in.offered); + + torture_comment(tctx, "Testing GetPrinterData(%s)\n", r.in.value_name); + + status = dcerpc_spoolss_GetPrinterData_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetPrinterData failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + if (expected_type) { + torture_assert_int_equal(tctx, type, *expected_type, "unexpected type"); + } + r.in.offered = needed; + r.out.data = talloc_zero_array(tctx, uint8_t, r.in.offered); + status = dcerpc_spoolss_GetPrinterData_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetPrinterData failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + talloc_asprintf(tctx, "GetPrinterData(%s) failed", r.in.value_name)); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_PrinterData, &data, type, needed, 1); + + if (type_p) { + *type_p = type; + } + + if (data_p) { + *data_p = r.out.data; + } + + if (needed_p) { + *needed_p = needed; + } + + return true; +} + +static bool test_GetPrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + return test_GetPrinterData_checktype(tctx, b, handle, value_name, + NULL, type_p, data_p, needed_p); +} + +static bool test_GetPrinterDataEx_checktype(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *key_name, + const char *value_name, + enum winreg_Type *expected_type, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + NTSTATUS status; + struct spoolss_GetPrinterDataEx r; + enum winreg_Type type; + uint32_t needed; + union spoolss_PrinterData data; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.handle = handle; + r.in.key_name = key_name; + r.in.value_name = value_name; + r.in.offered = 0; + r.out.type = &type; + r.out.needed = &needed; + r.out.data = talloc_zero_array(tctx, uint8_t, r.in.offered); + + torture_comment(tctx, "Testing GetPrinterDataEx(%s - %s)\n", + r.in.key_name, r.in.value_name); + + status = dcerpc_spoolss_GetPrinterDataEx_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + torture_skip(tctx, "GetPrinterDataEx not supported by server\n"); + } + torture_assert_ntstatus_ok(tctx, status, "GetPrinterDataEx failed"); + } + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + if (expected_type) { + torture_assert_int_equal(tctx, type, *expected_type, "unexpected type"); + } + r.in.offered = needed; + r.out.data = talloc_zero_array(tctx, uint8_t, r.in.offered); + status = dcerpc_spoolss_GetPrinterDataEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "GetPrinterDataEx failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + talloc_asprintf(tctx, "GetPrinterDataEx(%s - %s) failed", r.in.key_name, r.in.value_name)); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_PrinterData, &data, type, needed, 1); + + if (type_p) { + *type_p = type; + } + + if (data_p) { + *data_p = r.out.data; + } + + if (needed_p) { + *needed_p = needed; + } + + return true; +} + +static bool test_GetPrinterDataEx(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *key_name, + const char *value_name, + enum winreg_Type *type_p, + uint8_t **data_p, + uint32_t *needed_p) +{ + return test_GetPrinterDataEx_checktype(tctx, p, handle, key_name, value_name, + NULL, type_p, data_p, needed_p); +} + +static bool test_get_environment(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char **architecture) +{ + DATA_BLOB blob; + enum winreg_Type type; + uint8_t *data; + uint32_t needed; + + torture_assert(tctx, + test_GetPrinterData(tctx, b, handle, "Architecture", &type, &data, &needed), + "failed to get Architecture"); + + torture_assert_int_equal(tctx, type, REG_SZ, "unexpected type"); + + blob = data_blob_const(data, needed); + *architecture = reg_val_data_string(tctx, REG_SZ, blob); + + return true; +} + +static bool test_GetPrinterData_list(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *list[] = { + "W3SvcInstalled", + "BeepEnabled", + "EventLog", + /* "NetPopup", not on w2k8 */ + /* "NetPopupToComputer", not on w2k8 */ + "MajorVersion", + "MinorVersion", + "DefaultSpoolDirectory", + "Architecture", + "DsPresent", + "OSVersion", + /* "OSVersionEx", not on s3 */ + "DNSMachineName" + }; + int i; + + for (i=0; i < ARRAY_SIZE(list); i++) { + enum winreg_Type type = REG_NONE; + enum winreg_Type type_ex1 = REG_NONE; + enum winreg_Type type_ex2 = REG_NONE; + uint8_t *data; + uint8_t *data_ex1 = NULL; + uint8_t *data_ex2 = NULL; + uint32_t needed; + uint32_t needed_ex1 = 0; + uint32_t needed_ex2 = 0; + + torture_assert(tctx, test_GetPrinterData(tctx, b, &ctx->server_handle, list[i], &type, &data, &needed), + talloc_asprintf(tctx, "GetPrinterData failed on %s\n", list[i])); + torture_assert(tctx, test_GetPrinterDataEx(tctx, p, &ctx->server_handle, "random_string", list[i], &type_ex1, &data_ex1, &needed_ex1), + talloc_asprintf(tctx, "GetPrinterDataEx failed on %s\n", list[i])); + torture_assert(tctx, test_GetPrinterDataEx(tctx, p, &ctx->server_handle, "", list[i], &type_ex2, &data_ex2, &needed_ex2), + talloc_asprintf(tctx, "GetPrinterDataEx failed on %s\n", list[i])); + torture_assert_int_equal(tctx, type, type_ex1, "type mismatch"); + torture_assert_int_equal(tctx, type, type_ex2, "type mismatch"); + torture_assert_int_equal(tctx, needed, needed_ex1, "needed mismatch"); + torture_assert_int_equal(tctx, needed, needed_ex2, "needed mismatch"); + torture_assert_mem_equal(tctx, data, data_ex1, needed, "data mismatch"); + torture_assert_mem_equal(tctx, data, data_ex2, needed, "data mismatch"); + } + + return true; +} + +static bool test_EnumPrinterData(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + uint32_t enum_index, + uint32_t value_offered, + uint32_t data_offered, + enum winreg_Type *type_p, + uint32_t *value_needed_p, + uint32_t *data_needed_p, + const char **value_name_p, + uint8_t **data_p, + WERROR *result_p) +{ + struct spoolss_EnumPrinterData r; + uint32_t data_needed; + uint32_t value_needed; + enum winreg_Type type; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.handle = handle; + r.in.enum_index = enum_index; + r.in.value_offered = value_offered; + r.in.data_offered = data_offered; + r.out.data_needed = &data_needed; + r.out.value_needed = &value_needed; + r.out.type = &type; + r.out.data = talloc_zero_array(tctx, uint8_t, r.in.data_offered); + r.out.value_name = talloc_zero_array(tctx, const char, r.in.value_offered); + + torture_comment(tctx, "Testing EnumPrinterData(%d)\n", enum_index); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinterData_r(b, tctx, &r), + "EnumPrinterData failed"); + + if (type_p) { + *type_p = type; + } + if (value_needed_p) { + *value_needed_p = value_needed; + } + if (data_needed_p) { + *data_needed_p = data_needed; + } + if (value_name_p) { + *value_name_p = r.out.value_name; + } + if (data_p) { + *data_p = r.out.data; + } + if (result_p) { + *result_p = r.out.result; + } + + return true; +} + + +static bool test_EnumPrinterData_all(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + uint32_t enum_index = 0; + enum winreg_Type type; + uint32_t value_needed; + uint32_t data_needed; + uint8_t *data; + const char *value_name; + WERROR result; + + torture_comment(tctx, "Testing EnumPrinterData\n"); + + do { + torture_assert(tctx, + test_EnumPrinterData(tctx, p, handle, enum_index, 0, 0, + &type, &value_needed, &data_needed, + &value_name, &data, &result), + "EnumPrinterData failed"); + + if (W_ERROR_EQUAL(result, WERR_NO_MORE_ITEMS)) { + break; + } + + torture_assert(tctx, + test_EnumPrinterData(tctx, p, handle, enum_index, value_needed, data_needed, + &type, &value_needed, &data_needed, + &value_name, &data, &result), + "EnumPrinterData failed"); + + if (W_ERROR_EQUAL(result, WERR_NO_MORE_ITEMS)) { + break; + } + + enum_index++; + + } while (W_ERROR_IS_OK(result)); + + torture_comment(tctx, "EnumPrinterData test succeeded\n"); + + return true; +} + +static bool test_EnumPrinterDataEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + uint32_t *count_p, + struct spoolss_PrinterEnumValues **info_p) +{ + struct spoolss_EnumPrinterDataEx r; + struct spoolss_PrinterEnumValues *info; + uint32_t needed; + uint32_t count; + + r.in.handle = handle; + r.in.key_name = key_name; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrinterDataEx(%s)\n", key_name); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinterDataEx_r(b, tctx, &r), + "EnumPrinterDataEx failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinterDataEx_r(b, tctx, &r), + "EnumPrinterDataEx failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, "EnumPrinterDataEx failed"); + + CHECK_NEEDED_SIZE_ENUM(spoolss_EnumPrinterDataEx, info, count, needed, 1); + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + return true; +} + +static bool test_SetPrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type type, + uint8_t *data, + uint32_t offered); +static bool test_DeletePrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name); + +static bool test_EnumPrinterData_consistency(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + uint32_t count; + struct spoolss_PrinterEnumValues *info; + int i; + uint32_t value_needed, data_needed; + uint32_t value_offered, data_offered; + WERROR result; + struct dcerpc_binding_handle *b = p->binding_handle; + + enum winreg_Type type; + DATA_BLOB blob; + + torture_comment(tctx, "Testing EnumPrinterData vs EnumPrinterDataEx consistency\n"); + + torture_assert(tctx, push_reg_sz(tctx, &blob, "torture_data1"), ""); + type = REG_SZ; + + torture_assert(tctx, + test_SetPrinterData(tctx, b, handle, "torture_value1", type, blob.data, blob.length), + "SetPrinterData failed"); + + blob = data_blob_string_const("torture_data2"); + + torture_assert(tctx, + test_SetPrinterData(tctx, b, handle, "torture_value2", REG_BINARY, blob.data, blob.length), + "SetPrinterData failed"); + + blob = data_blob_talloc(tctx, NULL, 4); + SIVAL(blob.data, 0, 0x11223344); + + torture_assert(tctx, + test_SetPrinterData(tctx, b, handle, "torture_value3", type, blob.data, blob.length), + "SetPrinterData failed"); + + torture_assert(tctx, + test_EnumPrinterDataEx(tctx, b, handle, "PrinterDriverData", &count, &info), + "failed to call EnumPrinterDataEx"); + + /* get the max sizes for value and data */ + + torture_assert(tctx, + test_EnumPrinterData(tctx, p, handle, 0, 0, 0, + NULL, &value_needed, &data_needed, + NULL, NULL, &result), + "EnumPrinterData failed"); + torture_assert_werr_ok(tctx, result, "unexpected result"); + + /* check if the reply from the EnumPrinterData really matches max values */ + + for (i=0; i < count; i++) { + if (info[i].value_name_len > value_needed) { + torture_fail(tctx, + talloc_asprintf(tctx, + "EnumPrinterDataEx gave a reply with value length %d which is larger then expected max value length %d from EnumPrinterData", + info[i].value_name_len, value_needed)); + } + if (info[i].data_length > data_needed) { + torture_fail(tctx, + talloc_asprintf(tctx, + "EnumPrinterDataEx gave a reply with data length %d which is larger then expected max data length %d from EnumPrinterData", + info[i].data_length, data_needed)); + } + } + + /* assuming that both EnumPrinterData and EnumPrinterDataEx do either + * sort or not sort the replies by value name, we should be able to do + * the following entry comparison */ + + data_offered = data_needed; + value_offered = value_needed; + + for (i=0; i < count; i++) { + + const char *value_name; + uint8_t *data; + + torture_assert(tctx, + test_EnumPrinterData(tctx, p, handle, i, value_offered, data_offered, + &type, &value_needed, &data_needed, + &value_name, &data, &result), + "EnumPrinterData failed"); + + if (i -1 == count) { + torture_assert_werr_equal(tctx, result, WERR_NO_MORE_ITEMS, + "unexpected result"); + break; + } else { + torture_assert_werr_ok(tctx, result, "unexpected result"); + } + + torture_assert_int_equal(tctx, type, info[i].type, "type mismatch"); + torture_assert_int_equal(tctx, value_needed, info[i].value_name_len, "value name length mismatch"); + torture_assert_str_equal(tctx, value_name, info[i].value_name, "value name mismatch"); + torture_assert_int_equal(tctx, data_needed, info[i].data_length, "data length mismatch"); + torture_assert_mem_equal(tctx, data, info[i].data->data, info[i].data_length, "data mismatch"); + } + + torture_assert(tctx, + test_DeletePrinterData(tctx, b, handle, "torture_value1"), + "DeletePrinterData failed"); + torture_assert(tctx, + test_DeletePrinterData(tctx, b, handle, "torture_value2"), + "DeletePrinterData failed"); + torture_assert(tctx, + test_DeletePrinterData(tctx, b, handle, "torture_value3"), + "DeletePrinterData failed"); + + torture_comment(tctx, "EnumPrinterData vs EnumPrinterDataEx consistency test succeeded\n\n"); + + return true; +} + +static bool test_DeletePrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name) +{ + NTSTATUS status; + struct spoolss_DeletePrinterData r; + + r.in.handle = handle; + r.in.value_name = value_name; + + torture_comment(tctx, "Testing DeletePrinterData(%s)\n", + r.in.value_name); + + status = dcerpc_spoolss_DeletePrinterData_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "DeletePrinterData failed"); + torture_assert_werr_ok(tctx, r.out.result, "DeletePrinterData failed"); + + return true; +} + +static bool test_DeletePrinterDataEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char *value_name) +{ + struct spoolss_DeletePrinterDataEx r; + + r.in.handle = handle; + r.in.key_name = key_name; + r.in.value_name = value_name; + + torture_comment(tctx, "Testing DeletePrinterDataEx(%s - %s)\n", + r.in.key_name, r.in.value_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrinterDataEx_r(b, tctx, &r), + "DeletePrinterDataEx failed"); + torture_assert_werr_ok(tctx, r.out.result, + "DeletePrinterDataEx failed"); + + return true; +} + +static bool test_DeletePrinterKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name) +{ + struct spoolss_DeletePrinterKey r; + + r.in.handle = handle; + r.in.key_name = key_name; + + torture_comment(tctx, "Testing DeletePrinterKey(%s)\n", r.in.key_name); + + if (strequal(key_name, "") && !torture_setting_bool(tctx, "dangerous", false)) { + torture_skip(tctx, "not wiping out printer registry - enable dangerous tests to use\n"); + return true; + } + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrinterKey_r(b, tctx, &r), + "DeletePrinterKey failed"); + torture_assert_werr_ok(tctx, r.out.result, + "DeletePrinterKey failed"); + + return true; +} + +static bool test_winreg_OpenHKLM(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct winreg_OpenHKLM r; + + r.in.system_name = NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = handle; + + torture_comment(tctx, "Testing winreg_OpenHKLM\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_OpenHKLM_r(b, tctx, &r), "OpenHKLM failed"); + torture_assert_werr_ok(tctx, r.out.result, "OpenHKLM failed"); + + return true; +} + +static void init_winreg_String(struct winreg_String *name, const char *s) +{ + name->name = s; + if (s) { + name->name_len = 2 * (strlen_m(s) + 1); + name->name_size = name->name_len; + } else { + name->name_len = 0; + name->name_size = 0; + } +} + +static bool test_winreg_OpenKey_opts(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *hive_handle, + const char *keyname, + uint32_t options, + struct policy_handle *key_handle) +{ + struct winreg_OpenKey r; + + r.in.parent_handle = hive_handle; + init_winreg_String(&r.in.keyname, keyname); + r.in.options = options; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = key_handle; + + torture_comment(tctx, "Testing winreg_OpenKey(%s)\n", keyname); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_OpenKey_r(b, tctx, &r), "OpenKey failed"); + torture_assert_werr_ok(tctx, r.out.result, "OpenKey failed"); + + return true; +} + +static bool test_winreg_OpenKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *hive_handle, + const char *keyname, + struct policy_handle *key_handle) +{ + return test_winreg_OpenKey_opts(tctx, b, hive_handle, keyname, + REG_OPTION_NON_VOLATILE, key_handle); +} + +static bool test_winreg_CloseKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct winreg_CloseKey r; + + r.in.handle = handle; + r.out.handle = handle; + + torture_comment(tctx, "Testing winreg_CloseKey\n"); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_CloseKey_r(b, tctx, &r), "CloseKey failed"); + torture_assert_werr_ok(tctx, r.out.result, "CloseKey failed"); + + return true; +} + +bool test_winreg_QueryValue(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type *type_p, + uint32_t *data_size_p, + uint32_t *data_length_p, + uint8_t **data_p) +{ + struct winreg_QueryValue r; + enum winreg_Type type = REG_NONE; + uint32_t data_size = 0; + uint32_t data_length = 0; + struct winreg_String valuename; + uint8_t *data = NULL; + + init_winreg_String(&valuename, value_name); + + data = talloc_zero_array(tctx, uint8_t, 0); + + r.in.handle = handle; + r.in.value_name = &valuename; + r.in.type = &type; + r.in.data_size = &data_size; + r.in.data_length = &data_length; + r.in.data = data; + r.out.type = &type; + r.out.data = data; + r.out.data_size = &data_size; + r.out.data_length = &data_length; + + torture_comment(tctx, "Testing winreg_QueryValue(%s)\n", value_name); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), "QueryValue failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + *r.in.data_size = *r.out.data_size; + data = talloc_zero_array(tctx, uint8_t, *r.in.data_size); + r.in.data = data; + r.out.data = data; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), "QueryValue failed"); + } + torture_assert_werr_ok(tctx, r.out.result, "QueryValue failed"); + + if (type_p) { + *type_p = *r.out.type; + } + if (data_size_p) { + *data_size_p = *r.out.data_size; + } + if (data_length_p) { + *data_length_p = *r.out.data_length; + } + if (data_p) { + *data_p = r.out.data; + } + + return true; +} + +static bool test_winreg_query_printerdata(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *printer_name, + const char *key_name, + const char *value_name, + enum winreg_Type *w_type, + uint32_t *w_size, + uint32_t *w_length, + uint8_t **w_data) +{ + const char *printer_key; + struct policy_handle key_handle; + + printer_key = talloc_asprintf(tctx, "%s\\%s\\%s", + TOP_LEVEL_PRINT_PRINTERS_KEY, printer_name, key_name); + + torture_assert(tctx, + test_winreg_OpenKey(tctx, b, handle, printer_key, &key_handle), ""); + + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, &key_handle, value_name, w_type, w_size, w_length, w_data), ""); + + torture_assert(tctx, + test_winreg_CloseKey(tctx, b, &key_handle), ""); + + return true; +} + +static bool test_GetForm_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char *form_name, + enum winreg_Type *w_type, + uint32_t *w_size, + uint32_t *w_length, + uint8_t **w_data) +{ + struct policy_handle key_handle; + + torture_assert(tctx, + test_winreg_OpenKey(tctx, b, handle, key_name, &key_handle), ""); + + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, &key_handle, form_name, w_type, w_size, w_length, w_data), ""); + + torture_assert(tctx, + test_winreg_CloseKey(tctx, b, &key_handle), ""); + + return true; +} + +static bool test_winreg_symbolic_link(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *symlink_keyname, + const char *symlink_destination) +{ + /* check if the first key is a symlink to the second key */ + + enum winreg_Type w_type; + uint32_t w_size; + uint32_t w_length; + uint8_t *w_data; + struct policy_handle key_handle; + DATA_BLOB blob; + const char *str; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skip winreg symlink test against samba"); + } + + torture_assert(tctx, + test_winreg_OpenKey_opts(tctx, b, handle, symlink_keyname, REG_OPTION_OPEN_LINK, &key_handle), + "failed to open key link"); + + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, &key_handle, + "SymbolicLinkValue", + &w_type, &w_size, &w_length, &w_data), + "failed to query for 'SymbolicLinkValue' attribute"); + + torture_assert_int_equal(tctx, w_type, REG_LINK, "unexpected type"); + + blob = data_blob(w_data, w_size); + str = reg_val_data_string(tctx, REG_SZ, blob); + + torture_assert_str_equal(tctx, str, symlink_destination, "unexpected symlink target string"); + + torture_assert(tctx, + test_winreg_CloseKey(tctx, b, &key_handle), + "failed to close key link"); + + return true; +} + +static const char *strip_unc(const char *unc) +{ + char *name; + + if (!unc) { + return NULL; + } + + if (unc[0] == '\\' && unc[1] == '\\') { + unc +=2; + } + + name = strchr(unc, '\\'); + if (name) { + return name+1; + } + + return unc; +} + +static bool test_GetPrinterInfo_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *printer_name, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + union spoolss_PrinterInfo info; + const char *keys[] = { + TOP_LEVEL_CONTROL_PRINTERS_KEY, + TOP_LEVEL_PRINT_PRINTERS_KEY + }; + int i; + const char *printername, *sharename; + + torture_comment(tctx, "Testing Printer Info and winreg consistency\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to get printer info level 2"); + + printername = strip_unc(info.info2.printername); + sharename = strip_unc(info.info2.sharename); + +#define test_sz(wname, iname) \ +do {\ + DATA_BLOB blob;\ + const char *str;\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_SZ, "unexpected type");\ + blob = data_blob(w_data, w_size);\ + str = reg_val_data_string(tctx, REG_SZ, blob);\ + if (w_size == 2 && iname == NULL) {\ + /*torture_comment(tctx, "%s: \"\", %s: (null)\n", #wname, #iname);\ */\ + } else {\ + torture_assert_str_equal(tctx, str, iname,\ + talloc_asprintf(tctx, "%s - %s mismatch", #wname, #iname));\ + }\ +} while(0); + +#define test_dword(wname, iname) \ +do {\ + uint32_t value;\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_DWORD, "unexpected type");\ + torture_assert_int_equal(tctx, w_size, 4, "unexpected size");\ + torture_assert_int_equal(tctx, w_length, 4, "unexpected length");\ + value = IVAL(w_data, 0);\ + torture_assert_int_equal(tctx, value, iname,\ + talloc_asprintf(tctx, "%s - %s mismatch", #wname, #iname));\ +} while(0); + +#define test_binary(wname, iname) \ +do {\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type");\ + torture_assert_int_equal(tctx, w_size, iname.length, "unexpected length");\ + torture_assert_mem_equal(tctx, w_data, iname.data, w_size, \ + "binary unequal");\ +} while(0); + + +#define test_dm(wname, iname) \ +do {\ + DATA_BLOB blob;\ + struct spoolss_DeviceMode dm;\ + enum ndr_err_code ndr_err;\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type");\ + blob = data_blob(w_data, w_size);\ + ndr_err = ndr_pull_struct_blob(&blob, tctx, &dm,\ + (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);\ + torture_assert_ndr_success(tctx, ndr_err, "failed to unmarshall dm");\ + torture_assert(tctx, test_devicemode_equal(tctx, &dm, iname),\ + "dm unequal");\ +} while(0); + +#define test_sd(wname, iname) \ +do {\ + DATA_BLOB blob;\ + struct security_descriptor sd;\ + enum ndr_err_code ndr_err;\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_BINARY, "unexpected type");\ + blob = data_blob(w_data, w_size);\ + ndr_err = ndr_pull_struct_blob(&blob, tctx, &sd,\ + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);\ + torture_assert_ndr_success(tctx, ndr_err, "failed to unmarshall sd");\ + torture_assert(tctx, test_security_descriptor_equal(tctx, &sd, iname),\ + "sd unequal");\ +} while(0); + +#define test_multi_sz(wname, iname) \ +do {\ + DATA_BLOB blob;\ + const char **array;\ + enum winreg_Type w_type;\ + uint32_t w_size;\ + uint32_t w_length;\ + uint8_t *w_data;\ + int i;\ + torture_assert(tctx,\ + test_winreg_QueryValue(tctx, winreg_handle, &key_handle, wname,\ + &w_type, &w_size, &w_length, &w_data),\ + "failed to query winreg");\ + torture_assert_int_equal(tctx, w_type, REG_MULTI_SZ, "unexpected type");\ + blob = data_blob(w_data, w_size);\ + torture_assert(tctx, \ + pull_reg_multi_sz(tctx, &blob, &array),\ + "failed to pull multi sz");\ + for (i=0; array[i] != NULL; i++) {\ + torture_assert_str_equal(tctx, array[i], iname[i],\ + talloc_asprintf(tctx, "%s - %s mismatch", #wname, iname[i]));\ + }\ +} while(0); + + if (!test_winreg_symbolic_link(tctx, winreg_handle, hive_handle, + TOP_LEVEL_CONTROL_PRINTERS_KEY, + "\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers")) + { + torture_warning(tctx, "failed to check for winreg symlink"); + } + + for (i=0; i < ARRAY_SIZE(keys); i++) { + + const char *printer_key; + struct policy_handle key_handle; + + printer_key = talloc_asprintf(tctx, "%s\\%s", + keys[i], printer_name); + + torture_assert(tctx, + test_winreg_OpenKey(tctx, winreg_handle, hive_handle, printer_key, &key_handle), ""); + + test_sz("Name", printername); + test_sz("Share Name", sharename); + test_sz("Port", info.info2.portname); + test_sz("Printer Driver", info.info2.drivername); + test_sz("Description", info.info2.comment); + test_sz("Location", info.info2.location); + test_sz("Separator File", info.info2.sepfile); + test_sz("Print Processor", info.info2.printprocessor); + test_sz("Datatype", info.info2.datatype); + test_sz("Parameters", info.info2.parameters); + /* winreg: 0, spoolss not */ +/* test_dword("Attributes", info.info2.attributes); */ + test_dword("Priority", info.info2.priority); + test_dword("Default Priority", info.info2.defaultpriority); + /* winreg: 60, spoolss: 0 */ +/* test_dword("StartTime", info.info2.starttime); */ +/* test_dword("UntilTime", info.info2.untiltime); */ + /* winreg != spoolss */ +/* test_dword("Status", info.info2.status); */ + test_dm("Default DevMode", info.info2.devmode); + test_sd("Security", info.info2.secdesc); + + torture_assert(tctx, + test_winreg_CloseKey(tctx, winreg_handle, &key_handle), ""); + } + +#undef test_dm + + torture_comment(tctx, "Printer Info and winreg consistency test succeeded\n\n"); + + return true; +} + +static bool test_GetPrintserverInfo_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + union spoolss_PrinterInfo info; + struct policy_handle key_handle; + + torture_comment(tctx, + "Testing Printserver Info and winreg consistency\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 3, &info), + "failed to get printer info level 2"); + + torture_assert(tctx, + test_winreg_OpenKey(tctx, winreg_handle, hive_handle, + TOP_LEVEL_CONTROL_KEY, &key_handle), ""); + + test_sd("ServerSecurityDescriptor", info.info3.secdesc); + + torture_assert(tctx, + test_winreg_CloseKey(tctx, winreg_handle, &key_handle), ""); + +#undef test_sd + + torture_comment(tctx, + "Printserver Info and winreg consistency test succeeded\n\n"); + + return true; +} + + +static bool test_PrintProcessors(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *environment, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + union spoolss_PrintProcessorInfo *info; + uint32_t count; + int i; + + torture_comment(tctx, "Testing Print Processor Info and winreg consistency\n"); + + torture_assert(tctx, + test_EnumPrintProcessors_level(tctx, b, environment, 1, &count, &info, WERR_OK), + "failed to enum print processors level 1"); + + for (i=0; i < count; i++) { + + const char *processor_key; + struct policy_handle key_handle; + + processor_key = talloc_asprintf(tctx, "%s\\%s\\Print Processors\\%s", + TOP_LEVEL_CONTROL_ENVIRONMENTS_KEY, + environment, + info[i].info1.print_processor_name); + + torture_assert(tctx, + test_winreg_OpenKey(tctx, winreg_handle, hive_handle, processor_key, &key_handle), ""); + + /* nothing to check in there so far */ + + torture_assert(tctx, + test_winreg_CloseKey(tctx, winreg_handle, &key_handle), ""); + } + + torture_comment(tctx, "Print Processor Info and winreg consistency test succeeded\n\n"); + + return true; +} + +static bool test_GetPrinterDriver2_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *driver_name, + const char *architecture, + uint32_t level, + uint32_t client_major_version, + uint32_t client_minor_version, + union spoolss_DriverInfo *info_p, + WERROR *result); + +static const char *strip_path(const char *path) +{ + char *p; + + if (path == NULL) { + return NULL; + } + + p = strrchr(path, '\\'); + if (p) { + return p+1; + } + + return path; +} + +static const char **strip_paths(const char **path_array) +{ + int i; + + if (path_array == NULL) { + return NULL; + } + + for (i=0; path_array[i] != NULL; i++) { + path_array[i] = strip_path(path_array[i]); + } + + return path_array; +} + +static const char *driver_winreg_date(TALLOC_CTX *mem_ctx, NTTIME nt) +{ + time_t t; + struct tm *tm; + + if (nt == 0) { + return talloc_strdup(mem_ctx, "01/01/1601"); + } + + t = nt_time_to_unix(nt); + tm = localtime(&t); + + return talloc_asprintf(mem_ctx, "%02d/%02d/%04d", + tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900); +} + +static const char *driver_winreg_version(TALLOC_CTX *mem_ctx, uint64_t v) +{ + return talloc_asprintf(mem_ctx, "%u.%u.%u.%u", + (unsigned)((v >> 48) & 0xFFFF), + (unsigned)((v >> 32) & 0xFFFF), + (unsigned)((v >> 16) & 0xFFFF), + (unsigned)(v & 0xFFFF)); +} + +static bool test_GetDriverInfo_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *printer_name, + const char *driver_name, + const char *environment, + enum spoolss_DriverOSVersion version, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle, + const char *server_name_slash) +{ + WERROR result = WERR_OK; + union spoolss_DriverInfo info; + const char *driver_key; + struct policy_handle key_handle; + + const char *driver_path; + const char *data_file; + const char *config_file; + const char *help_file; + const char **dependent_files; + + const char *driver_date; + const char *inbox_driver_date; + + const char *driver_version; + const char *inbox_driver_version; + + ZERO_STRUCT(key_handle); + + torture_comment(tctx, "Testing Driver Info and winreg consistency\n"); + + driver_key = talloc_asprintf(tctx, "%s\\%s\\Drivers\\Version-%d\\%s", + TOP_LEVEL_CONTROL_ENVIRONMENTS_KEY, + environment, + version, + driver_name); + + torture_assert(tctx, + test_winreg_OpenKey(tctx, winreg_handle, hive_handle, driver_key, &key_handle), + "failed to open driver key"); + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "w2k3", false)) { + goto try_level6; + } + + if (handle) { + torture_assert(tctx, + test_GetPrinterDriver2_level(tctx, b, handle, driver_name, environment, 8, version, 0, &info, &result), + "failed to get driver info level 8"); + } else { + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, environment, 8, driver_name, &info), + "failed to get driver info level 8"); + } + + if (W_ERROR_EQUAL(result, WERR_INVALID_LEVEL)) { + goto try_level6; + } + + driver_path = strip_path(info.info8.driver_path); + data_file = strip_path(info.info8.data_file); + config_file = strip_path(info.info8.config_file); + help_file = strip_path(info.info8.help_file); + dependent_files = strip_paths(info.info8.dependent_files); + + driver_date = driver_winreg_date(tctx, info.info8.driver_date); + inbox_driver_date = driver_winreg_date(tctx, info.info8.min_inbox_driver_ver_date); + + driver_version = driver_winreg_version(tctx, info.info8.driver_version); + inbox_driver_version = driver_winreg_version(tctx, info.info8.min_inbox_driver_ver_version); + + test_sz("Configuration File", config_file); + test_sz("Data File", data_file); + test_sz("Datatype", info.info8.default_datatype); + test_sz("Driver", driver_path); + test_sz("DriverDate", driver_date); + test_sz("DriverVersion", driver_version); + test_sz("HardwareID", info.info8.hardware_id); + test_sz("Help File", help_file); + test_sz("InfPath", info.info8.inf_path); + test_sz("Manufacturer", info.info8.manufacturer_name); + test_sz("MinInboxDriverVerDate", inbox_driver_date); + test_sz("MinInboxDriverVerVersion", inbox_driver_version); + test_sz("Monitor", info.info8.monitor_name); + test_sz("OEM URL", info.info8.manufacturer_url); + test_sz("Print Processor", info.info8.print_processor); + test_sz("Provider", info.info8.provider); + test_sz("VendorSetup", info.info8.vendor_setup); + test_multi_sz("ColorProfiles", info.info8.color_profiles); + test_multi_sz("Dependent Files", dependent_files); + test_multi_sz("CoreDependencies", info.info8.core_driver_dependencies); + test_multi_sz("Previous Names", info.info8.previous_names); +/* test_dword("Attributes", ?); */ + test_dword("PrinterDriverAttributes", info.info8.printer_driver_attributes); + test_dword("Version", info.info8.version); +/* test_dword("TempDir", ?); */ + + try_level6: + + if (handle) { + torture_assert(tctx, + test_GetPrinterDriver2_level(tctx, b, handle, driver_name, environment, 6, version, 0, &info, &result), + "failed to get driver info level 6"); + } else { + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, environment, 6, driver_name, &info), + "failed to get driver info level 6"); + } + + driver_path = strip_path(info.info6.driver_path); + data_file = strip_path(info.info6.data_file); + config_file = strip_path(info.info6.config_file); + help_file = strip_path(info.info6.help_file); + dependent_files = strip_paths(info.info6.dependent_files); + + driver_date = driver_winreg_date(tctx, info.info6.driver_date); + + driver_version = driver_winreg_version(tctx, info.info6.driver_version); + + test_sz("Configuration File", config_file); + test_sz("Data File", data_file); + test_sz("Datatype", info.info6.default_datatype); + test_sz("Driver", driver_path); + if (torture_setting_bool(tctx, "w2k3", false)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, 8); + push_nttime(blob.data, 0, info.info6.driver_date); + test_binary("DriverDate", blob); + SBVAL(blob.data, 0, info.info6.driver_version); + test_binary("DriverVersion", blob); + } else { + test_sz("DriverDate", driver_date); + test_sz("DriverVersion", driver_version); + } + test_sz("HardwareID", info.info6.hardware_id); + test_sz("Help File", help_file); + test_sz("Manufacturer", info.info6.manufacturer_name); + test_sz("Monitor", info.info6.monitor_name); + test_sz("OEM URL", info.info6.manufacturer_url); + test_sz("Provider", info.info6.provider); + test_multi_sz("Dependent Files", dependent_files); + test_multi_sz("Previous Names", info.info6.previous_names); +/* test_dword("Attributes", ?); */ + test_dword("Version", info.info6.version); +/* test_dword("TempDir", ?); */ + + if (handle) { + torture_assert(tctx, + test_GetPrinterDriver2_level(tctx, b, handle, driver_name, environment, 3, version, 0, &info, &result), + "failed to get driver info level 3"); + } else { + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, environment, 3, driver_name, &info), + "failed to get driver info level 3"); + } + + driver_path = strip_path(info.info3.driver_path); + data_file = strip_path(info.info3.data_file); + config_file = strip_path(info.info3.config_file); + help_file = strip_path(info.info3.help_file); + dependent_files = strip_paths(info.info3.dependent_files); + + test_sz("Configuration File", config_file); + test_sz("Data File", data_file); + test_sz("Datatype", info.info3.default_datatype); + test_sz("Driver", driver_path); + test_sz("Help File", help_file); + test_sz("Monitor", info.info3.monitor_name); + test_multi_sz("Dependent Files", dependent_files); +/* test_dword("Attributes", ?); */ + test_dword("Version", info.info3.version); +/* test_dword("TempDir", ?); */ + + + torture_assert(tctx, + test_winreg_CloseKey(tctx, winreg_handle, &key_handle), ""); + + torture_comment(tctx, "Driver Info and winreg consistency test succeeded\n\n"); + + return true; +} + +#undef test_sz +#undef test_dword + +static bool test_SetPrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type type, + uint8_t *data, + uint32_t offered) +{ + struct spoolss_SetPrinterData r; + + r.in.handle = handle; + r.in.value_name = value_name; + r.in.type = type; + r.in.data = data; + r.in.offered = offered; + + torture_comment(tctx, "Testing SetPrinterData(%s)\n", + r.in.value_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_SetPrinterData_r(b, tctx, &r), + "SetPrinterData failed"); + torture_assert_werr_ok(tctx, r.out.result, + "SetPrinterData failed"); + + return true; +} + +static bool test_SetPrinterData_matrix(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *printer_name, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + const char *values[] = { + "spootyfoot", + "spooty\\foot", +#if 0 + /* FIXME: not working with s3 atm. */ + "spooty,foot", + "spooty,fo,ot", +#endif + "spooty foot", +#if 0 + /* FIXME: not working with s3 atm. */ + "spooty\\fo,ot", + "spooty,fo\\ot" +#endif + }; + int i; + + for (i=0; i < ARRAY_SIZE(values); i++) { + + enum winreg_Type type, expected_type = REG_SZ; + DATA_BLOB blob; + uint8_t *data; + uint32_t needed; + + torture_assert(tctx, push_reg_sz(tctx, &blob, "dog"), ""); + type = REG_SZ; + + torture_assert(tctx, + test_SetPrinterData(tctx, b, handle, values[i], REG_SZ, blob.data, blob.length), + "SetPrinterData failed"); + + torture_assert(tctx, + test_GetPrinterData_checktype(tctx, b, handle, values[i], &expected_type, &type, &data, &needed), + "GetPrinterData failed"); + + torture_assert_int_equal(tctx, type, REG_SZ, "type mismatch"); + torture_assert_int_equal(tctx, needed, blob.length, "size mismatch"); + torture_assert_mem_equal(tctx, data, blob.data, blob.length, "buffer mismatch"); + + if (winreg_handle && hive_handle) { + + enum winreg_Type w_type; + uint32_t w_size; + uint32_t w_length; + uint8_t *w_data; + + torture_assert(tctx, + test_winreg_query_printerdata(tctx, winreg_handle, hive_handle, + printer_name, "PrinterDriverData", values[i], + &w_type, &w_size, &w_length, &w_data), ""); + + torture_assert_int_equal(tctx, w_type, REG_SZ, "winreg type mismatch"); + torture_assert_int_equal(tctx, w_size, blob.length, "winreg size mismatch"); + torture_assert_int_equal(tctx, w_length, blob.length, "winreg length mismatch"); + torture_assert_mem_equal(tctx, w_data, blob.data, blob.length, "winreg buffer mismatch"); + } + + torture_assert(tctx, + test_DeletePrinterData(tctx, b, handle, values[i]), + "DeletePrinterData failed"); + } + + return true; +} + + +static bool test_EnumPrinterKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char ***array); + +static bool test_SetPrinterDataEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char *value_name, + enum winreg_Type type, + uint8_t *data, + uint32_t offered) +{ + NTSTATUS status; + struct spoolss_SetPrinterDataEx r; + + r.in.handle = handle; + r.in.key_name = key_name; + r.in.value_name = value_name; + r.in.type = type; + r.in.data = data; + r.in.offered = offered; + + torture_comment(tctx, "Testing SetPrinterDataEx(%s - %s) type: %s, offered: 0x%08x\n", + r.in.key_name, r.in.value_name, str_regtype(r.in.type), r.in.offered); + + status = dcerpc_spoolss_SetPrinterDataEx_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "SetPrinterDataEx failed"); + torture_assert_werr_ok(tctx, r.out.result, "SetPrinterDataEx failed"); + + return true; +} + +static bool test_SetPrinterDataEx_keys(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + const char *value_name = "dog"; + const char *keys[] = { + "torturedataex", + "torture data ex", + "torturedataex_with_subkey\\subkey", + "torturedataex_with_subkey\\subkey:0", + "torturedataex_with_subkey\\subkey:1", + "torturedataex_with_subkey\\subkey\\subsubkey", + "torturedataex_with_subkey\\subkey\\subsubkey:0", + "torturedataex_with_subkey\\subkey\\subsubkey:1", + "torture,data", + "torture,data,ex", + "torture,data\\ex", + "torture\\data,ex", + "torture/data", + "torture/data ex", + "torture/data ex/sub", + "torture//data", + "torture//data ex", + "torture//data ex/sub", + "torture//data ex//sub", + }; + int i; + + for (i=0; i < ARRAY_SIZE(keys); i++) { + + char *c; + const char *key; + enum winreg_Type type; + DATA_BLOB blob_in, blob_out; + const char **subkeys; + uint32_t ecount; + struct spoolss_PrinterEnumValues *einfo; + uint32_t needed; + + blob_in = data_blob_talloc(tctx, NULL, 42); + + generate_random_buffer(blob_in.data, blob_in.length); + + torture_assert(tctx, + test_SetPrinterDataEx(tctx, b, handle, keys[i], value_name, REG_BINARY, blob_in.data, blob_in.length), + "failed to call SetPrinterDataEx"); + + torture_assert(tctx, + test_GetPrinterDataEx(tctx, p, handle, keys[i], value_name, &type, &blob_out.data, &needed), + "failed to call GetPrinterDataEx"); + + blob_out.length = needed; + torture_assert(tctx, + test_EnumPrinterDataEx(tctx, b, handle, keys[i], &ecount, &einfo), + "failed to call EnumPrinterDataEx"); + + torture_assert_int_equal(tctx, type, REG_BINARY, "type mismatch"); + torture_assert_int_equal(tctx, blob_out.length, blob_in.length, "size mismatch"); + torture_assert_mem_equal(tctx, blob_out.data, blob_in.data, blob_in.length, "buffer mismatch"); + + torture_assert_int_equal(tctx, ecount, 1, "unexpected enum count"); + torture_assert_str_equal(tctx, einfo[0].value_name, value_name, "value_name mismatch"); + torture_assert_int_equal(tctx, einfo[0].value_name_len, strlen_m_term(value_name)*2, "unexpected value_name_len"); + torture_assert_int_equal(tctx, einfo[0].type, REG_BINARY, "type mismatch"); + torture_assert_int_equal(tctx, einfo[0].data_length, blob_in.length, "size mismatch"); + if (einfo[0].data_length > 0) { + torture_assert_mem_equal(tctx, einfo[0].data->data, blob_in.data, blob_in.length, "buffer mismatch"); + } + + key = talloc_strdup(tctx, keys[i]); + + if (!test_DeletePrinterDataEx(tctx, b, handle, keys[i], value_name)) { + return false; + } + + c = strchr(key, '\\'); + if (c) { + int k; + + /* we have subkeys */ + + *c = 0; + + if (!test_EnumPrinterKey(tctx, b, handle, key, &subkeys)) { + return false; + } + + for (k=0; subkeys && subkeys[k]; k++) { + + const char *current_key = talloc_asprintf(tctx, "%s\\%s", key, subkeys[k]); + + if (!test_DeletePrinterKey(tctx, b, handle, current_key)) { + return false; + } + } + + if (!test_DeletePrinterKey(tctx, b, handle, key)) { + return false; + } + + } else { + if (!test_DeletePrinterKey(tctx, b, handle, key)) { + return false; + } + } + } + + return true; +} + +static bool test_SetPrinterDataEx_values(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + const char *key = "torturedataex"; + const char *values[] = { + "torture_value", + "torture value", + "torture,value", + "torture/value", + "torture\\value", + "torture\\\\value" + }; + int i; + + for (i=0; i < ARRAY_SIZE(values); i++) { + + enum winreg_Type type = REG_NONE; + DATA_BLOB blob_in = data_blob_null; + DATA_BLOB blob_out = data_blob_null; + uint32_t ecount; + struct spoolss_PrinterEnumValues *einfo; + uint32_t needed = 0; + + if (torture_setting_bool(tctx, "samba3", false)) { + char *q; + q = strrchr(values[i], ','); + if (q) { + torture_comment(tctx, "skipping valuename '%s' including ',' character against Samba3\n", + values[i]); + continue; + } + } + + blob_in = data_blob_talloc(tctx, NULL, 42); + + generate_random_buffer(blob_in.data, blob_in.length); + + torture_assert(tctx, + test_SetPrinterDataEx(tctx, b, handle, key, values[i], REG_BINARY, blob_in.data, blob_in.length), + "failed to call SetPrinterDataEx"); + + torture_assert(tctx, + test_GetPrinterDataEx(tctx, p, handle, key, values[i], &type, &blob_out.data, &needed), + "failed to call GetPrinterDataEx"); + + blob_out.length = needed; + torture_assert(tctx, + test_EnumPrinterDataEx(tctx, b, handle, key, &ecount, &einfo), + "failed to call EnumPrinterDataEx"); + + torture_assert_int_equal(tctx, type, REG_BINARY, "type mismatch"); + torture_assert_int_equal(tctx, blob_out.length, blob_in.length, "size mismatch"); + torture_assert_mem_equal(tctx, blob_out.data, blob_in.data, blob_in.length, "buffer mismatch"); + + torture_assert_int_equal(tctx, ecount, 1, "unexpected enum count"); + torture_assert_str_equal(tctx, einfo[0].value_name, values[i], "value_name mismatch"); + torture_assert_int_equal(tctx, einfo[0].value_name_len, strlen_m_term(values[i])*2, "unexpected value_name_len"); + torture_assert_int_equal(tctx, einfo[0].type, REG_BINARY, "type mismatch"); + torture_assert_int_equal(tctx, einfo[0].data_length, blob_in.length, "size mismatch"); + if (einfo[0].data_length > 0) { + torture_assert_mem_equal(tctx, einfo[0].data->data, blob_in.data, blob_in.length, "buffer mismatch"); + } + + torture_assert(tctx, + test_DeletePrinterDataEx(tctx, b, handle, key, values[i]), + "failed to call DeletePrinterDataEx"); + } + + return true; +} + + +static bool test_SetPrinterDataEx_matrix(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printername, + struct dcerpc_binding_handle *winreg_handle, + struct policy_handle *hive_handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + const char *value_name = "dog"; + const char *key_name = "torturedataex"; + enum winreg_Type types[] = { + REG_SZ, + REG_MULTI_SZ, + REG_DWORD, + REG_BINARY + }; + const char *str = "abcdefghi"; + size_t t, s; + + for (t=0; t < ARRAY_SIZE(types); t++) { + for (s=0; s < strlen(str); s++) { + + enum winreg_Type type; + const char *string = talloc_strndup(tctx, str, s); + const char *array[2]; + DATA_BLOB blob = data_blob_string_const(string); + DATA_BLOB data; + uint8_t *data_out; + uint32_t needed, offered = 0; + uint32_t ecount; + struct spoolss_PrinterEnumValues *einfo; + + array[0] = talloc_strdup(tctx, string); + array[1] = NULL; + + if (types[t] == REG_DWORD) { + s = 0xffff; + } + + switch (types[t]) { + case REG_BINARY: + data = blob; + offered = blob.length; + break; + case REG_DWORD: + data = data_blob_talloc(tctx, NULL, 4); + SIVAL(data.data, 0, 0x12345678); + offered = 4; + break; + case REG_SZ: + torture_assert(tctx, push_reg_sz(tctx, &data, string), ""); + type = REG_SZ; + offered = data.length; + /*strlen_m_term(data.string)*2;*/ + break; + case REG_MULTI_SZ: + torture_assert(tctx, push_reg_multi_sz(tctx, &data, array), ""); + type = REG_MULTI_SZ; + offered = data.length; + break; + default: + torture_fail(tctx, talloc_asprintf(tctx, "type %d untested\n", types[t])); + } + + torture_assert(tctx, + test_SetPrinterDataEx(tctx, b, handle, key_name, value_name, types[t], data.data, offered), + "failed to call SetPrinterDataEx"); + + torture_assert(tctx, + test_GetPrinterDataEx_checktype(tctx, p, handle, key_name, value_name, &types[t], &type, &data_out, &needed), + "failed to call GetPrinterDataEx"); + + torture_assert(tctx, + test_EnumPrinterDataEx(tctx, b, handle, key_name, &ecount, &einfo), + "failed to call EnumPrinterDataEx"); + + torture_assert_int_equal(tctx, types[t], type, "type mismatch"); + torture_assert_int_equal(tctx, needed, offered, "size mismatch"); + torture_assert_mem_equal(tctx, data_out, data.data, offered, "buffer mismatch"); + + torture_assert_int_equal(tctx, ecount, 1, "unexpected enum count"); + torture_assert_str_equal(tctx, einfo[0].value_name, value_name, "value_name mismatch"); + torture_assert_int_equal(tctx, einfo[0].value_name_len, strlen_m_term(value_name)*2, "unexpected value_name_len"); + torture_assert_int_equal(tctx, einfo[0].type, types[t], "type mismatch"); + torture_assert_int_equal(tctx, einfo[0].data_length, offered, "size mismatch"); + if (einfo[0].data_length > 0) { + torture_assert_mem_equal(tctx, einfo[0].data->data, data.data, offered, "buffer mismatch"); + } + + if (winreg_handle && hive_handle) { + enum winreg_Type w_type; + uint32_t w_size; + uint32_t w_length; + uint8_t *w_data; + + torture_assert(tctx, + test_winreg_query_printerdata(tctx, winreg_handle, hive_handle, + printername, key_name, value_name, + &w_type, &w_size, &w_length, &w_data), ""); + + torture_assert_int_equal(tctx, w_type, types[t], "winreg type mismatch"); + torture_assert_int_equal(tctx, w_size, offered, "winreg size mismatch"); + torture_assert_int_equal(tctx, w_length, offered, "winreg length mismatch"); + torture_assert_mem_equal(tctx, w_data, data.data, offered, "winreg buffer mismatch"); + } + + torture_assert(tctx, + test_DeletePrinterDataEx(tctx, b, handle, key_name, value_name), + "failed to call DeletePrinterDataEx"); + } + } + + return true; +} + +static bool test_PrinterData_winreg(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printer_name) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret &= test_SetPrinterData_matrix(tctx, b, handle, printer_name, b2, &hive_handle); + ret &= test_SetPrinterDataEx_matrix(tctx, p, handle, printer_name, b2, &hive_handle); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + +static bool test_Forms_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + bool print_server, + const char *printer_name) +{ + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_Forms(tctx, b, handle, print_server, printer_name, b2, &hive_handle); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + +static bool test_PrinterInfo_winreg(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printer_name) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_GetPrinterInfo_winreg(tctx, b, handle, printer_name, b2, &hive_handle); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + +static bool test_PrintserverInfo_winreg(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_GetPrintserverInfo_winreg(tctx, b, handle, b2, &hive_handle); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + + +static bool test_DriverInfo_winreg(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printer_name, + const char *driver_name, + const char *environment, + enum spoolss_DriverOSVersion version) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_GetDriverInfo_winreg(tctx, b, handle, printer_name, driver_name, environment, version, b2, &hive_handle, NULL); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + +static bool test_PrintProcessors_winreg(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *environment) +{ + struct dcerpc_pipe *p2; + bool ret = true; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_PrintProcessors(tctx, b, environment, b2, &hive_handle); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + + return ret; +} + +static bool test_PrinterData_DsSpooler(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printer_name) +{ + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + union spoolss_SetPrinterInfo sinfo; + union spoolss_PrinterInfo info; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *pname; + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + torture_comment(tctx, "Testing DsSpooler <-> SetPrinter relations\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to query Printer level 2"); + + torture_assert(tctx, + PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), + "failed to convert"); + + info_ctr.level = 2; + info_ctr.info = sinfo; + +#define TEST_SZ(wname, iname) \ +do {\ + enum winreg_Type type;\ + uint8_t *data;\ + uint32_t needed;\ + DATA_BLOB blob;\ + const char *str;\ + torture_assert(tctx,\ + test_GetPrinterDataEx(tctx, p, handle, "DsSpooler", wname, &type, &data, &needed),\ + "failed to query");\ + torture_assert_int_equal(tctx, type, REG_SZ, "unexpected type");\ + blob = data_blob_const(data, needed);\ + torture_assert(tctx,\ + pull_reg_sz(tctx, &blob, &str),\ + "failed to pull REG_SZ");\ + torture_assert_str_equal(tctx, str, iname, "unexpected result");\ +} while(0); + + +#define TEST_SET_SZ(wname, iname, val) \ +do {\ + enum winreg_Type type;\ + uint8_t *data;\ + uint32_t needed;\ + DATA_BLOB blob;\ + const char *str;\ + sinfo.info2->iname = val;\ + torture_assert(tctx,\ + test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0),\ + "failed to call SetPrinter");\ + torture_assert(tctx,\ + test_GetPrinterDataEx(tctx, p, handle, "DsSpooler", wname, &type, &data, &needed),\ + "failed to query");\ + torture_assert_int_equal(tctx, type, REG_SZ, "unexpected type");\ + blob = data_blob_const(data, needed);\ + torture_assert(tctx,\ + pull_reg_sz(tctx, &blob, &str),\ + "failed to pull REG_SZ");\ + torture_assert_str_equal(tctx, str, val, "unexpected result");\ +} while(0); + +#define TEST_SET_DWORD(wname, iname, val) \ +do {\ + enum winreg_Type type;\ + uint8_t *data;\ + uint32_t needed;\ + uint32_t value;\ + sinfo.info2->iname = val;\ + torture_assert(tctx,\ + test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0),\ + "failed to call SetPrinter");\ + torture_assert(tctx,\ + test_GetPrinterDataEx(tctx, p, handle, "DsSpooler", wname, &type, &data, &needed),\ + "failed to query");\ + torture_assert_int_equal(tctx, type, REG_DWORD, "unexpected type");\ + torture_assert_int_equal(tctx, needed, 4, "unexpected length");\ + value = IVAL(data, 0); \ + torture_assert_int_equal(tctx, value, val, "unexpected result");\ +} while(0); + + TEST_SET_SZ("description", comment, "newval"); + TEST_SET_SZ("location", location, "newval"); + TEST_SET_SZ("driverName", drivername, "newval"); +/* TEST_SET_DWORD("priority", priority, 25); */ + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to query Printer level 2"); + + TEST_SZ("description", info.info2.comment); + TEST_SZ("driverName", info.info2.drivername); + TEST_SZ("location", info.info2.location); + + pname = strrchr(info.info2.printername, '\\'); + if (pname == NULL) { + pname = info.info2.printername; + } else { + pname++; + } + TEST_SZ("printerName", pname); + /* TEST_SZ("printSeparatorFile", info.info2.sepfile); */ + /* TEST_SZ("printShareName", info.info2.sharename); */ + + /* FIXME gd: complete the list */ + +#undef TEST_SZ +#undef TEST_SET_SZ +#undef TEST_DWORD + + torture_comment(tctx, "DsSpooler <-> SetPrinter relations test succeeded\n\n"); + + return true; +} + +static bool test_print_processors_winreg(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_PrintProcessors_winreg(tctx, b, ctx->environment); +} + +static bool test_AddPrintProcessor(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *environment, + const char *path_name, + const char *print_processor_name, + WERROR expected_error) +{ + struct spoolss_AddPrintProcessor r; + + r.in.server = NULL; + r.in.architecture = environment; + r.in.path_name = path_name; + r.in.print_processor_name = print_processor_name; + + torture_comment(tctx, "Testing AddPrintProcessor(%s)\n", + print_processor_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_AddPrintProcessor_r(b, tctx, &r), + "spoolss_AddPrintProcessor failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_error, + "spoolss_AddPrintProcessor failed"); + + return true; +} + +static bool test_DeletePrintProcessor(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *environment, + const char *print_processor_name, + WERROR expected_error) +{ + struct spoolss_DeletePrintProcessor r; + + r.in.server = NULL; + r.in.architecture = environment; + r.in.print_processor_name = print_processor_name; + + torture_comment(tctx, "Testing DeletePrintProcessor(%s)\n", + print_processor_name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrintProcessor_r(b, tctx, &r), + "spoolss_DeletePrintProcessor failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_error, + "spoolss_DeletePrintProcessor failed"); + + return true; +} + +static bool test_add_print_processor(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + int i; + + struct { + const char *environment; + const char *path_name; + const char *print_processor_name; + WERROR expected_add_result; + WERROR expected_del_result; + } tests[] = { + { + .environment = ctx->environment, + .path_name = "", + .print_processor_name = "winprint", + .expected_add_result = WERR_PRINT_PROCESSOR_ALREADY_INSTALLED, + .expected_del_result = WERR_CAN_NOT_COMPLETE + },{ + .environment = ctx->environment, + .path_name = "", + .print_processor_name = "unknown", + .expected_add_result = WERR_MOD_NOT_FOUND, + .expected_del_result = WERR_UNKNOWN_PRINTPROCESSOR + } + }; + + for (i=0; i < ARRAY_SIZE(tests); i++) { + torture_assert(tctx, + test_AddPrintProcessor(tctx, b, + tests[i].environment, + tests[i].path_name, + tests[i].print_processor_name, + tests[i].expected_add_result), + "add print processor failed"); + torture_assert(tctx, + test_DeletePrintProcessor(tctx, b, + tests[i].environment, + tests[i].print_processor_name, + tests[i].expected_del_result), + "delete print processor failed"); + } + + return true; +} + +static bool test_AddPerMachineConnection(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, + const char *printername, + const char *printserver, + const char *provider, + WERROR expected_error) +{ + struct spoolss_AddPerMachineConnection r; + const char *composed_printername = printername; + + if (servername != NULL) { + composed_printername = talloc_asprintf(tctx, "%s\\%s", + servername, + printername); + } + r.in.server = servername; + r.in.printername = composed_printername; + r.in.printserver = printserver; + r.in.provider = provider; + + torture_comment(tctx, "Testing AddPerMachineConnection(%s|%s|%s)\n", + printername, printserver, provider); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_AddPerMachineConnection_r(b, tctx, &r), + "spoolss_AddPerMachineConnection failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_error, + "spoolss_AddPerMachineConnection failed"); + + return true; +} + +static bool test_DeletePerMachineConnection(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, + const char *printername, + WERROR expected_error) +{ + struct spoolss_DeletePerMachineConnection r; + const char *composed_printername = printername; + + if (servername != NULL) { + composed_printername = talloc_asprintf(tctx, "%s\\%s", + servername, + printername); + } + + r.in.server = servername; + r.in.printername = composed_printername; + + torture_comment(tctx, "Testing DeletePerMachineConnection(%s)\n", + printername); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePerMachineConnection_r(b, tctx, &r), + "spoolss_DeletePerMachineConnection failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_error, + "spoolss_DeletePerMachineConnection failed"); + + return true; +} + +static bool test_EnumPerMachineConnections(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername) +{ + struct spoolss_EnumPerMachineConnections r; + DATA_BLOB blob = data_blob_null; + struct spoolss_PrinterInfo4 *info; + uint32_t needed; + uint32_t count; + + r.in.server = servername; + r.in.buffer = &blob; + r.in.offered = 0; + + r.out.info = &info; + r.out.needed = &needed; + r.out.count = &count; + + torture_comment(tctx, "Testing EnumPerMachineConnections(%s)\n", + servername); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPerMachineConnections_r(b, tctx, &r), + "spoolss_EnumPerMachineConnections failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPerMachineConnections_r(b, tctx, &r), + "spoolss_EnumPerMachineConnections failed"); + } + torture_assert_werr_ok(tctx, r.out.result, + "spoolss_EnumPerMachineConnections failed"); + + return true; +} + +static bool test_addpermachineconnection(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + int i; + + struct { + const char *servername; + const char *printername; + const char *printserver; + const char *provider; + WERROR expected_add_result; + WERROR expected_del_result; + } tests[] = { + { + .servername = NULL, + .printername = "foo", + .printserver = "", + .provider = "unknown", + .expected_add_result = WERR_INVALID_PRINTER_NAME, + .expected_del_result = WERR_INVALID_PRINTER_NAME + },{ + .servername = NULL, + .printername = "Microsoft Print to PDF", + .printserver = "samba.org", + .provider = "unknown", + .expected_add_result = WERR_INVALID_PRINTER_NAME, + .expected_del_result = WERR_INVALID_PRINTER_NAME + },{ + .servername = NULL, + .printername = "Microsoft Print to PDF", + .printserver = "samba.org", + .provider = "", + .expected_add_result = WERR_INVALID_PRINTER_NAME, + .expected_del_result = WERR_INVALID_PRINTER_NAME + },{ + .servername = server_name_slash, + .printername = "foo", + .printserver = "", + .provider = "unknown", + .expected_add_result = WERR_FILE_NOT_FOUND, + .expected_del_result = WERR_INVALID_PRINTER_NAME + },{ + .servername = server_name_slash, + .printername = "foo", + .printserver = "", + .provider = "", + .expected_add_result = WERR_OK, + .expected_del_result = WERR_OK + },{ + .servername = server_name_slash, + .printername = "Microsoft Print to PDF", + .printserver = "samba.org", + .provider = "unknown", + .expected_add_result = WERR_FILE_NOT_FOUND, + .expected_del_result = WERR_INVALID_PRINTER_NAME + },{ + .servername = server_name_slash, + .printername = "Microsoft Print to PDF", + .printserver = "samba.org", + .provider = "", + .expected_add_result = WERR_OK, + .expected_del_result = WERR_OK + } + }; + + for (i=0; i < ARRAY_SIZE(tests); i++) { + torture_assert(tctx, + test_AddPerMachineConnection(tctx, b, + tests[i].servername, + tests[i].printername, + tests[i].printserver, + tests[i].provider, + tests[i].expected_add_result), + "add per machine connection failed"); + torture_assert(tctx, + test_EnumPerMachineConnections(tctx, b, + tests[i].servername), + "enum per machine connections failed"); + torture_assert(tctx, + test_DeletePerMachineConnection(tctx, b, + tests[i].servername, + tests[i].printername, + tests[i].expected_del_result), + "delete per machine connection failed"); + torture_assert(tctx, + test_EnumPerMachineConnections(tctx, b, + tests[i].servername), + "enum per machine connections failed"); + } + + return true; +} + +static bool test_GetChangeID_PrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t *change_id) +{ + enum winreg_Type type; + uint8_t *data; + uint32_t needed; + + torture_assert(tctx, + test_GetPrinterData(tctx, b, handle, "ChangeID", &type, &data, &needed), + "failed to call GetPrinterData"); + + torture_assert(tctx, type == REG_DWORD, "unexpected type"); + torture_assert_int_equal(tctx, needed, 4, "unexpected size"); + + *change_id = IVAL(data, 0); + + return true; +} + +static bool test_GetChangeID_PrinterDataEx(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + uint32_t *change_id) +{ + enum winreg_Type type = REG_NONE; + uint8_t *data = NULL; + uint32_t needed = 0; + + torture_assert(tctx, + test_GetPrinterDataEx(tctx, p, handle, "PrinterDriverData", "ChangeID", &type, &data, &needed), + "failed to call GetPrinterData"); + + torture_assert(tctx, type == REG_DWORD, "unexpected type"); + torture_assert_int_equal(tctx, needed, 4, "unexpected size"); + + *change_id = IVAL(data, 0); + + return true; +} + +static bool test_GetChangeID_PrinterInfo(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t *change_id) +{ + union spoolss_PrinterInfo info; + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 0, &info), + "failed to query Printer level 0"); + + *change_id = info.info0.change_id; + + return true; +} + +static bool test_ChangeID(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + uint32_t change_id, change_id_ex, change_id_info; + uint32_t change_id2, change_id_ex2, change_id_info2; + union spoolss_PrinterInfo info; + const char *comment; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing ChangeID: id change test #1\n"); + + torture_assert(tctx, test_GetChangeID_PrinterData(tctx, b, handle, &change_id), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterDataEx(tctx, p, handle, &change_id_ex), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterInfo(tctx, b, handle, &change_id_info), + "failed to query for ChangeID"); + + torture_assert_int_equal(tctx, change_id, change_id_ex, + "change_ids should all be equal"); + torture_assert_int_equal(tctx, change_id_ex, change_id_info, + "change_ids should all be equal"); + + + torture_comment(tctx, "Testing ChangeID: id change test #2\n"); + + torture_assert(tctx, test_GetChangeID_PrinterData(tctx, b, handle, &change_id), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to query Printer level 2"); + torture_assert(tctx, test_GetChangeID_PrinterDataEx(tctx, p, handle, &change_id_ex), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterInfo(tctx, b, handle, &change_id_info), + "failed to query for ChangeID"); + torture_assert_int_equal(tctx, change_id, change_id_ex, + "change_id should not have changed"); + torture_assert_int_equal(tctx, change_id_ex, change_id_info, + "change_id should not have changed"); + + + torture_comment(tctx, "Testing ChangeID: id change test #3\n"); + + torture_assert(tctx, test_GetChangeID_PrinterData(tctx, b, handle, &change_id), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterDataEx(tctx, p, handle, &change_id_ex), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterInfo(tctx, b, handle, &change_id_info), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), + "failed to query Printer level 2"); + comment = talloc_strdup(tctx, info.info2.comment); + + { + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + union spoolss_SetPrinterInfo sinfo; + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + + torture_assert(tctx, PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), ""); + sinfo.info2->comment = "torture_comment"; + + info_ctr.level = 2; + info_ctr.info = sinfo; + + torture_assert(tctx, test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), + "failed to call SetPrinter"); + + sinfo.info2->comment = comment; + + torture_assert(tctx, test_SetPrinter(tctx, b, handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), + "failed to call SetPrinter"); + + } + + torture_assert(tctx, test_GetChangeID_PrinterData(tctx, b, handle, &change_id2), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterDataEx(tctx, p, handle, &change_id_ex2), + "failed to query for ChangeID"); + torture_assert(tctx, test_GetChangeID_PrinterInfo(tctx, b, handle, &change_id_info2), + "failed to query for ChangeID"); + + torture_assert_int_equal(tctx, change_id2, change_id_ex2, + "change_ids should all be equal"); + torture_assert_int_equal(tctx, change_id_ex2, change_id_info2, + "change_ids should all be equal"); + + torture_assert(tctx, (change_id < change_id2), + talloc_asprintf(tctx, "change_id %d needs to be larger than change_id %d", + change_id2, change_id)); + torture_assert(tctx, (change_id_ex < change_id_ex2), + talloc_asprintf(tctx, "change_id %d needs to be larger than change_id %d", + change_id_ex2, change_id_ex)); + torture_assert(tctx, (change_id_info < change_id_info2), + talloc_asprintf(tctx, "change_id %d needs to be larger than change_id %d", + change_id_info2, change_id_info)); + + torture_comment(tctx, "ChangeID tests succeeded\n\n"); + + return true; +} + +static bool test_SecondaryClosePrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + NTSTATUS status; + struct cli_credentials *anon_creds; + const struct dcerpc_binding *binding2; + struct dcerpc_pipe *p2; + struct spoolss_ClosePrinter cp; + + /* only makes sense on SMB */ + if (p->conn->transport.transport != NCACN_NP) { + return true; + } + + torture_comment(tctx, "Testing close on secondary pipe\n"); + + anon_creds = cli_credentials_init_anon(tctx); + torture_assert(tctx, anon_creds != NULL, "cli_credentials_init_anon failed"); + + binding2 = p->binding; + status = dcerpc_secondary_auth_connection(p, binding2, &ndr_table_spoolss, + anon_creds, tctx->lp_ctx, + tctx, &p2); + torture_assert_ntstatus_ok(tctx, status, "Failed to create secondary connection"); + + cp.in.handle = handle; + cp.out.handle = handle; + + status = dcerpc_spoolss_ClosePrinter_r(p2->binding_handle, tctx, &cp); + torture_assert_ntstatus_equal(tctx, status, NT_STATUS_RPC_SS_CONTEXT_MISMATCH, + "ERROR: Allowed close on secondary connection"); + + talloc_free(p2); + + return true; +} + +static bool test_OpenPrinter_badname(struct torture_context *tctx, + struct dcerpc_binding_handle *b, const char *name) +{ + NTSTATUS status; + struct spoolss_OpenPrinter op; + struct spoolss_OpenPrinterEx opEx; + struct policy_handle handle; + bool ret = true; + + op.in.printername = name; + op.in.datatype = NULL; + op.in.devmode_ctr.devmode= NULL; + op.in.access_mask = 0; + op.out.handle = &handle; + + torture_comment(tctx, "Testing OpenPrinter(%s) with bad name\n", op.in.printername); + + status = dcerpc_spoolss_OpenPrinter_r(b, tctx, &op); + torture_assert_ntstatus_ok(tctx, status, "OpenPrinter failed"); + torture_assert_werr_equal(tctx, op.out.result, WERR_INVALID_PRINTER_NAME, + "unexpected result"); + + if (W_ERROR_IS_OK(op.out.result)) { + ret &=test_ClosePrinter(tctx, b, &handle); + } + + opEx.in.printername = name; + opEx.in.datatype = NULL; + opEx.in.devmode_ctr.devmode = NULL; + opEx.in.access_mask = 0; + opEx.in.userlevel_ctr.level = 1; + opEx.in.userlevel_ctr.user_info.level1 = NULL; + opEx.out.handle = &handle; + + torture_comment(tctx, "Testing OpenPrinterEx(%s) with bad name\n", opEx.in.printername); + + status = dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &opEx); + torture_assert_ntstatus_ok(tctx, status, "OpenPrinterEx failed"); + torture_assert_werr_equal(tctx, opEx.out.result, WERR_INVALID_PARAMETER, + "unexpected result"); + + if (W_ERROR_IS_OK(opEx.out.result)) { + ret &=test_ClosePrinter(tctx, b, &handle); + } + + return ret; +} + +static bool test_OpenPrinter_badname_list(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + const char *badnames[] = { + "__INVALID_PRINTER__", + "\\\\__INVALID_HOST__", + "", + "\\\\\\", + "\\\\\\__INVALID_PRINTER__" + }; + const char *badname; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + const char *server_name = dcerpc_server_name(p); + struct dcerpc_binding_handle *b = p->binding_handle; + int i; + + for (i=0; i < ARRAY_SIZE(badnames); i++) { + torture_assert(tctx, + test_OpenPrinter_badname(tctx, b, badnames[i]), + ""); + } + + badname = talloc_asprintf(tctx, "\\\\%s\\", server_name); + torture_assert(tctx, + test_OpenPrinter_badname(tctx, b, badname), + ""); + + badname = talloc_asprintf(tctx, "\\\\%s\\__INVALID_PRINTER__", server_name); + torture_assert(tctx, + test_OpenPrinter_badname(tctx, b, badname), + ""); + + return true; +} + +static bool test_OpenPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + const char *environment, + bool open_only) +{ + NTSTATUS status; + struct spoolss_OpenPrinter r; + struct policy_handle handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.printername = name; + r.in.datatype = NULL; + r.in.devmode_ctr.devmode= NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_comment(tctx, "Testing OpenPrinter(%s)\n", r.in.printername); + + status = dcerpc_spoolss_OpenPrinter_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "OpenPrinter failed"); + + torture_assert_werr_ok(tctx, r.out.result, "OpenPrinter failed"); + + if (open_only) { + goto close_printer; + } + + if (!test_GetPrinter(tctx, b, &handle, environment)) { + ret = false; + } + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (!test_SecondaryClosePrinter(tctx, p, &handle)) { + ret = false; + } + } + + close_printer: + if (!test_ClosePrinter(tctx, b, &handle)) { + ret = false; + } + + return ret; +} + +static bool test_OpenPrinterEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *printername, + const char *datatype, + struct spoolss_DeviceMode *devmode, + uint32_t access_mask, + struct spoolss_UserLevelCtr *userlevel_ctr, + struct policy_handle *handle, + WERROR expected_result) +{ + struct spoolss_OpenPrinterEx r; + + r.in.printername = printername; + r.in.datatype = datatype; + r.in.devmode_ctr.devmode= devmode; + r.in.access_mask = access_mask; + r.in.userlevel_ctr = *userlevel_ctr; + r.out.handle = handle; + + torture_comment(tctx, "Testing OpenPrinterEx(%s)\n", r.in.printername); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &r), + "OpenPrinterEx failed"); + + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "OpenPrinterEx failed"); + + return true; +} + +static bool call_OpenPrinterEx(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + struct spoolss_DeviceMode *devmode, + struct policy_handle *handle) +{ + struct spoolss_UserLevelCtr userlevel_ctr; + struct spoolss_UserLevel1 userlevel1; + struct dcerpc_binding_handle *b = p->binding_handle; + + userlevel1.size = 1234; + userlevel1.client = "hello"; + userlevel1.user = "spottyfoot!"; + userlevel1.build = 1; + userlevel1.major = 2; + userlevel1.minor = 3; + userlevel1.processor = 4; + + userlevel_ctr.level = 1; + userlevel_ctr.user_info.level1 = &userlevel1; + + return test_OpenPrinterEx(tctx, b, name, NULL, devmode, + SEC_FLAG_MAXIMUM_ALLOWED, + &userlevel_ctr, + handle, + WERR_OK); +} + +static bool test_printer_rename(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + bool ret = true; + union spoolss_PrinterInfo info; + union spoolss_SetPrinterInfo sinfo; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + const char *printer_name; + const char *printer_name_orig; + const char *printer_name_new = "SAMBA smbtorture Test Printer (Copy 2)"; + struct policy_handle new_handle; + const char *q; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + torture_comment(tctx, "Testing Printer rename operations\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &t->handle, 2, &info), + "failed to call GetPrinter level 2"); + + printer_name_orig = talloc_strdup(tctx, info.info2.printername); + + q = strrchr(info.info2.printername, '\\'); + if (q) { + torture_warning(tctx, + "server returns printername %s incl. servername although we did not set servername", info.info2.printername); + } + + torture_assert(tctx, + PrinterInfo_to_SetPrinterInfo(tctx, &info, 2, &sinfo), ""); + + sinfo.info2->printername = printer_name_new; + + info_ctr.level = 2; + info_ctr.info = sinfo; + + torture_assert(tctx, + test_SetPrinter(tctx, b, &t->handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), + "failed to call SetPrinter level 2"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &t->handle, 2, &info), + "failed to call GetPrinter level 2"); + + printer_name = talloc_strdup(tctx, info.info2.printername); + + q = strrchr(info.info2.printername, '\\'); + if (q) { + torture_warning(tctx, + "server returns printername %s incl. servername although we did not set servername", info.info2.printername); + q++; + printer_name = q; + } + + torture_assert_str_equal(tctx, printer_name, printer_name_new, + "new printer name was not set"); + + /* samba currently cannot fully rename printers */ + if (!torture_setting_bool(tctx, "samba3", false)) { + torture_assert(tctx, + test_OpenPrinter_badname(tctx, b, printer_name_orig), + "still can open printer with oldname after rename"); + } else { + torture_warning(tctx, "*not* checking for open with oldname after rename for samba3"); + } + + torture_assert(tctx, + call_OpenPrinterEx(tctx, p, printer_name_new, NULL, &new_handle), + "failed to open printer with new name"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &new_handle, 2, &info), + "failed to call GetPrinter level 2"); + + torture_assert_str_equal(tctx, info.info2.printername, printer_name_new, + "new printer name was not set"); + + torture_assert(tctx, + test_ClosePrinter(tctx, b, &new_handle), + "failed to close printer"); + + torture_comment(tctx, "Printer rename operations test succeeded\n\n"); + + return ret; +} + +static bool test_openprinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *real_printername) +{ + struct spoolss_UserLevelCtr userlevel_ctr; + struct policy_handle handle; + struct spoolss_UserLevel1 userlevel1; + const char *printername = NULL; + int i; + + struct { + const char *suffix; + WERROR expected_result; + } tests[] = { + { + .suffix = "rubbish", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", LocalOnl", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", localOnly", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", localonl", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ",LocalOnl", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ",localOnl2", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", DrvConver2t", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", drvconvert", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ",drvconvert", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ", DrvConvert", + .expected_result = WERR_OK + },{ + .suffix = " , DrvConvert", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ",DrvConvert", + .expected_result = WERR_OK + },{ + .suffix = ", DrvConvertsadfasdf", + .expected_result = WERR_OK + },{ + .suffix = ",DrvConvertasdfasd", + .expected_result = WERR_OK + },{ + .suffix = ", LocalOnly", + .expected_result = WERR_OK + },{ + .suffix = " , LocalOnly", + .expected_result = WERR_INVALID_PRINTER_NAME + },{ + .suffix = ",LocalOnly", + .expected_result = WERR_OK + },{ + .suffix = ", LocalOnlysagi4gjfkd", + .expected_result = WERR_OK + },{ + .suffix = ",LocalOnlysagi4gjfkd", + .expected_result = WERR_OK + } + }; + + userlevel1.size = 1234; + userlevel1.client = "hello"; + userlevel1.user = "spottyfoot!"; + userlevel1.build = 1; + userlevel1.major = 2; + userlevel1.minor = 3; + userlevel1.processor = 4; + + userlevel_ctr.level = 1; + userlevel_ctr.user_info.level1 = &userlevel1; + + torture_comment(tctx, "Testing openprinterex printername pattern\n"); + + torture_assert(tctx, + test_OpenPrinterEx(tctx, b, real_printername, NULL, NULL, 0, + &userlevel_ctr, &handle, + WERR_OK), + "OpenPrinterEx failed"); + test_ClosePrinter(tctx, b, &handle); + + for (i=0; i < ARRAY_SIZE(tests); i++) { + + printername = talloc_asprintf(tctx, "%s%s", + real_printername, + tests[i].suffix); + + torture_assert(tctx, + test_OpenPrinterEx(tctx, b, printername, NULL, NULL, 0, + &userlevel_ctr, &handle, + tests[i].expected_result), + "OpenPrinterEx failed"); + if (W_ERROR_IS_OK(tests[i].expected_result)) { + test_ClosePrinter(tctx, b, &handle); + } + } + + return true; +} + + +static bool test_existing_printer_openprinterex(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + const char *environment) +{ + struct policy_handle handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_openprinter(tctx, b, name)) { + return false; + } + + if (!call_OpenPrinterEx(tctx, p, name, NULL, &handle)) { + return false; + } + + if (!test_PrinterInfo_SD(tctx, b, &handle)) { + ret = false; + } + + if (!test_GetPrinter(tctx, b, &handle, environment)) { + ret = false; + } + + if (!test_EnumForms_all(tctx, b, &handle, false)) { + ret = false; + } + + if (!test_Forms(tctx, b, &handle, false, name, NULL, NULL)) { + ret = false; + } + + if (!test_Forms_winreg(tctx, b, &handle, false, name)) { + ret = false; + } + + if (!test_EnumPrinterData_all(tctx, p, &handle)) { + ret = false; + } + + if (!test_EnumPrinterDataEx(tctx, b, &handle, "PrinterDriverData", NULL, NULL)) { + ret = false; + } + + if (!test_EnumPrinterData_consistency(tctx, p, &handle)) { + ret = false; + } + + if (!test_printer_all_keys(tctx, b, &handle)) { + ret = false; + } + + if (!test_PausePrinter(tctx, b, &handle)) { + ret = false; + } + + if (!test_DoPrintTest(tctx, b, &handle)) { + ret = false; + } + + if (!test_ResumePrinter(tctx, b, &handle)) { + ret = false; + } + + if (!test_SetPrinterData_matrix(tctx, b, &handle, name, NULL, NULL)) { + ret = false; + } + + if (!test_SetPrinterDataEx_matrix(tctx, p, &handle, name, NULL, NULL)) { + ret = false; + } + + if (!torture_setting_bool(tctx, "samba3", false)) { + if (!test_SecondaryClosePrinter(tctx, p, &handle)) { + ret = false; + } + } + + if (!test_ClosePrinter(tctx, b, &handle)) { + ret = false; + } + + return ret; +} + +static bool test_EnumPrinters_old(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct spoolss_EnumPrinters r; + NTSTATUS status; + uint16_t levels[] = {1, 2, 4, 5}; + int i; + bool ret = true; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + union spoolss_PrinterInfo *info; + int j; + uint32_t needed; + uint32_t count; + + r.in.flags = PRINTER_ENUM_LOCAL; + r.in.server = ""; + r.in.level = levels[i]; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrinters level %u\n", r.in.level); + + status = dcerpc_spoolss_EnumPrinters_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "EnumPrinters failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + status = dcerpc_spoolss_EnumPrinters_r(b, tctx, &r); + } + + torture_assert_ntstatus_ok(tctx, status, "EnumPrinters failed"); + + torture_assert_werr_ok(tctx, r.out.result, "EnumPrinters failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrinters, info, r.in.level, count, needed, 4); + + if (!info) { + torture_comment(tctx, "No printers returned\n"); + return true; + } + + for (j=0;j<count;j++) { + if (r.in.level == 1) { + char *unc = talloc_strdup(tctx, info[j].info1.name); + char *slash, *name, *full_name; + name = unc; + if (unc[0] == '\\' && unc[1] == '\\') { + unc +=2; + } + slash = strchr(unc, '\\'); + if (slash) { + slash++; + name = slash; + } + full_name = talloc_asprintf(tctx, "\\\\%s\\%s", + dcerpc_server_name(p), name); + if (!test_OpenPrinter(tctx, p, name, ctx->environment, true)) { + ret = false; + } + if (!test_OpenPrinter(tctx, p, full_name, ctx->environment, true)) { + ret = false; + } + if (!test_OpenPrinter(tctx, p, name, ctx->environment, false)) { + ret = false; + } + if (!test_existing_printer_openprinterex(tctx, p, name, ctx->environment)) { + ret = false; + } + } + } + } + + return ret; +} + +static bool test_EnumPrinters_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t flags, + const char *servername, + uint32_t level, + uint32_t *count_p, + union spoolss_PrinterInfo **info_p) +{ + struct spoolss_EnumPrinters r; + union spoolss_PrinterInfo *info; + uint32_t needed; + uint32_t count; + + r.in.flags = flags; + r.in.server = servername; + r.in.level = level; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + r.out.count = &count; + r.out.info = &info; + + torture_comment(tctx, "Testing EnumPrinters(%s) level %u\n", + r.in.server, r.in.level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "EnumPrinters failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "EnumPrinters failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, "EnumPrinters failed"); + + CHECK_NEEDED_SIZE_ENUM_LEVEL(spoolss_EnumPrinters, info, r.in.level, count, needed, 4); + + if (count_p) { + *count_p = count; + } + if (info_p) { + *info_p = info; + } + + return true; +} + +static const char *get_short_printername(struct torture_context *tctx, + const char *name) +{ + const char *short_name; + + if (name[0] == '\\' && name[1] == '\\') { + name += 2; + short_name = strchr(name, '\\'); + if (short_name) { + return talloc_strdup(tctx, short_name+1); + } + } + + return name; +} + +static const char *get_full_printername(struct torture_context *tctx, + const char *name) +{ + const char *full_name = talloc_strdup(tctx, name); + char *p; + + if (name && name[0] == '\\' && name[1] == '\\') { + name += 2; + p = strchr(name, '\\'); + if (p) { + return full_name; + } + } + + return NULL; +} + +static bool test_OnePrinter_servername(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct dcerpc_binding_handle *b, + const char *servername, + const char *printername) +{ + union spoolss_PrinterInfo info; + const char *short_name = get_short_printername(tctx, printername); + const char *full_name = get_full_printername(tctx, printername); + + if (short_name) { + struct policy_handle handle; + torture_assert(tctx, + call_OpenPrinterEx(tctx, p, short_name, NULL, &handle), + "failed to open printer"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &handle, 2, &info), + "failed to get printer info"); + + torture_assert_casestr_equal(tctx, info.info2.servername, NULL, + "unexpected servername"); + torture_assert_casestr_equal(tctx, info.info2.printername, short_name, + "unexpected printername"); + + if (info.info2.devmode) { + const char *expected_devicename; + expected_devicename = talloc_strndup(tctx, short_name, MIN(strlen(short_name), 31)); + torture_assert_casestr_equal(tctx, info.info2.devmode->devicename, expected_devicename, + "unexpected devicemode devicename"); + } + + torture_assert(tctx, + test_ClosePrinter(tctx, b, &handle), + "failed to close printer"); + } + + if (full_name) { + struct policy_handle handle; + + torture_assert(tctx, + call_OpenPrinterEx(tctx, p, full_name, NULL, &handle), + "failed to open printer"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &handle, 2, &info), + "failed to get printer info"); + + torture_assert_casestr_equal(tctx, info.info2.servername, servername, + "unexpected servername"); + torture_assert_casestr_equal(tctx, info.info2.printername, full_name, + "unexpected printername"); + + if (info.info2.devmode) { + const char *expected_devicename; + expected_devicename = talloc_strndup(tctx, full_name, MIN(strlen(full_name), 31)); + torture_assert_casestr_equal(tctx, info.info2.devmode->devicename, expected_devicename, + "unexpected devicemode devicename"); + } + + torture_assert(tctx, + test_ClosePrinter(tctx, b, &handle), + "failed to close printer"); + } + + return true; +} + +static bool test_EnumPrinters_servername(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t count; + union spoolss_PrinterInfo *info; + const char *servername; + uint32_t flags = PRINTER_ENUM_NAME|PRINTER_ENUM_LOCAL; + + torture_comment(tctx, "Testing servername behaviour in EnumPrinters and GetPrinters\n"); + + servername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + torture_assert(tctx, + test_EnumPrinters_level(tctx, b, flags, servername, 2, &count, &info), + "failed to enumerate printers"); + + for (i=0; i < count; i++) { + + torture_assert_casestr_equal(tctx, info[i].info2.servername, servername, + "unexpected servername"); + + torture_assert(tctx, + test_OnePrinter_servername(tctx, p, b, servername, info[i].info2.printername), + "failed to check printer"); + } + + servername = ""; + + torture_assert(tctx, + test_EnumPrinters_level(tctx, b, flags, servername, 2, &count, &info), + "failed to enumerate printers"); + + for (i=0; i < count; i++) { + + torture_assert_casestr_equal(tctx, info[i].info2.servername, NULL, + "unexpected servername"); + + torture_assert(tctx, + test_OnePrinter_servername(tctx, p, b, servername, info[i].info2.printername), + "failed to check printer"); + } + + + return true; +} + +#if 0 +static bool test_GetPrinterDriver(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *driver_name) +{ + struct spoolss_GetPrinterDriver r; + uint32_t needed; + + r.in.handle = handle; + r.in.architecture = "W32X86"; + r.in.level = 1; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_comment(tctx, "Testing GetPrinterDriver level %d\n", r.in.level); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_GetPrinterDriver_r(b, tctx, &r), + "failed to call GetPrinterDriver"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_GetPrinterDriver_r(b, tctx, &r), + "failed to call GetPrinterDriver"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "failed to call GetPrinterDriver"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_DriverInfo, r.out.info, r.in.level, needed, 4); + + return true; +} +#endif + +static bool test_GetPrinterDriver2_level(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *driver_name, + const char *architecture, + uint32_t level, + uint32_t client_major_version, + uint32_t client_minor_version, + union spoolss_DriverInfo *info_p, + WERROR *result_p) + +{ + struct spoolss_GetPrinterDriver2 r; + uint32_t needed; + uint32_t server_major_version; + uint32_t server_minor_version; + + r.in.handle = handle; + r.in.architecture = architecture; + r.in.client_major_version = client_major_version; + r.in.client_minor_version = client_minor_version; + r.in.buffer = NULL; + r.in.offered = 0; + r.in.level = level; + r.out.needed = &needed; + r.out.server_major_version = &server_major_version; + r.out.server_minor_version = &server_minor_version; + + torture_comment(tctx, "Testing GetPrinterDriver2(%s) level %d\n", + driver_name, r.in.level); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriver2_r(b, tctx, &r), + "failed to call GetPrinterDriver2"); + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriver2_r(b, tctx, &r), + "failed to call GetPrinterDriver2"); + } + + if (result_p) { + *result_p = r.out.result; + } + + if (W_ERROR_EQUAL(r.out.result, WERR_INVALID_LEVEL)) { + switch (r.in.level) { + case 101: + case 8: + torture_comment(tctx, + "level %d not implemented, not considering as an error\n", + r.in.level); + return true; + default: + break; + } + } + + torture_assert_werr_ok(tctx, r.out.result, + "failed to call GetPrinterDriver2"); + + CHECK_NEEDED_SIZE_LEVEL(spoolss_DriverInfo, r.out.info, r.in.level, needed, 4); + + if (info_p) { + *info_p = *r.out.info; + } + + return true; +} + +static bool test_GetPrinterDriver2(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *driver_name, + const char *architecture) +{ + uint16_t levels[] = {1, 2, 3, 4, 5, 6, 8, 101 }; + int i; + + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + torture_assert(tctx, + test_GetPrinterDriver2_level(tctx, b, handle, driver_name, architecture, levels[i], 3, 0, NULL, NULL), + ""); + } + + return true; +} + +static bool test_EnumPrinterDrivers_old(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + uint16_t levels[] = {1, 2, 3, 4, 5, 6}; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + uint32_t count; + union spoolss_DriverInfo *info; + + torture_assert(tctx, + test_EnumPrinterDrivers_args(tctx, b, server_name, ctx->environment, levels[i], &count, &info), + "failed to enumerate drivers"); + + if (!info) { + torture_comment(tctx, "No printer drivers returned\n"); + break; + } + } + + return true; +} + +static bool test_DeletePrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct spoolss_DeletePrinter r; + + torture_comment(tctx, "Testing DeletePrinter\n"); + + r.in.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_DeletePrinter_r(b, tctx, &r), + "failed to delete printer"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to delete printer"); + + return true; +} + +static bool test_EnumPrinters_findname(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t flags, + uint32_t level, + const char *name, + bool *found) +{ + struct spoolss_EnumPrinters e; + uint32_t count; + union spoolss_PrinterInfo *info; + uint32_t needed; + int i; + + *found = false; + + e.in.flags = flags; + e.in.server = NULL; + e.in.level = level; + e.in.buffer = NULL; + e.in.offered = 0; + e.out.count = &count; + e.out.info = &info; + e.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinters_r(b, tctx, &e), + "failed to enum printers"); + + if (W_ERROR_EQUAL(e.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + e.in.buffer = &blob; + e.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinters_r(b, tctx, &e), + "failed to enum printers"); + } + + torture_assert_werr_ok(tctx, e.out.result, + "failed to enum printers"); + + for (i=0; i < count; i++) { + + const char *current = NULL; + const char *q; + + switch (level) { + case 1: + current = info[i].info1.name; + break; + } + + if (strequal(current, name)) { + *found = true; + break; + } + + q = strrchr(current, '\\'); + if (q) { + if (!e.in.server) { + torture_warning(tctx, + "server returns printername %s incl. servername although we did not set servername", current); + } + q++; + if (strequal(q, name)) { + *found = true; + break; + } + } + } + + return true; +} + +static bool test_AddPrinter_wellknown(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *printername, + bool ex) +{ + WERROR result; + struct spoolss_AddPrinter r; + struct spoolss_AddPrinterEx rex; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo1 info1; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct spoolss_UserLevelCtr userlevel_ctr; + struct policy_handle handle; + bool found = false; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + ZERO_STRUCT(userlevel_ctr); + ZERO_STRUCT(info1); + + torture_comment(tctx, "Testing AddPrinter%s(%s) level 1\n", + ex ? "Ex":"", printername); + + /* try to add printer to wellknown printer list (level 1) */ + + userlevel_ctr.level = 1; + + info_ctr.info.info1 = &info1; + info_ctr.level = 1; + + rex.in.server = NULL; + rex.in.info_ctr = &info_ctr; + rex.in.devmode_ctr = &devmode_ctr; + rex.in.secdesc_ctr = &secdesc_ctr; + rex.in.userlevel_ctr = &userlevel_ctr; + rex.out.handle = &handle; + + r.in.server = NULL; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_INVALID_PRINTER_NAME, + "unexpected result code"); + + info1.name = printername; + info1.flags = PRINTER_ATTRIBUTE_SHARED; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_PRINTER_ALREADY_EXISTS, + "unexpected result code"); + + /* bizarre protocol, WERR_PRINTER_ALREADY_EXISTS means success here, + better do a real check to see the printer is really there */ + + torture_assert(tctx, test_EnumPrinters_findname(tctx, b, + PRINTER_ENUM_NETWORK, 1, + printername, + &found), + "failed to enum printers"); + + torture_assert(tctx, found, "failed to find newly added printer"); + + info1.flags = 0; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_PRINTER_ALREADY_EXISTS, + "unexpected result code"); + + /* bizarre protocol, WERR_PRINTER_ALREADY_EXISTS means success here, + better do a real check to see the printer has really been removed + from the well known printer list */ + + found = false; + + torture_assert(tctx, test_EnumPrinters_findname(tctx, b, + PRINTER_ENUM_NETWORK, 1, + printername, + &found), + "failed to enum printers"); +#if 0 + torture_assert(tctx, !found, "printer still in well known printer list"); +#endif + return true; +} + +static bool test_AddPrinter_normal(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle_p, + const char *printername, + const char *drivername, + const char *portname, + struct spoolss_DeviceMode *devmode, + bool ex) +{ + WERROR result; + struct spoolss_AddPrinter r; + struct spoolss_AddPrinterEx rex; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo2 info2; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct spoolss_UserLevelCtr userlevel_ctr; + struct policy_handle handle; + bool found = false; + bool existing_printer_deleted = false; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + ZERO_STRUCT(userlevel_ctr); + + torture_comment(tctx, "Testing AddPrinter%s(%s) level 2\n", + ex ? "Ex":"", printername); + + devmode_ctr.devmode = devmode; + + userlevel_ctr.level = 1; + + rex.in.server = NULL; + rex.in.info_ctr = &info_ctr; + rex.in.devmode_ctr = &devmode_ctr; + rex.in.secdesc_ctr = &secdesc_ctr; + rex.in.userlevel_ctr = &userlevel_ctr; + rex.out.handle = &handle; + + r.in.server = NULL; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.out.handle = &handle; + + again: + + /* try to add printer to printer list (level 2) */ + + ZERO_STRUCT(info2); + + info_ctr.info.info2 = &info2; + info_ctr.level = 2; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_INVALID_PRINTER_NAME, + "unexpected result code"); + + info2.printername = printername; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + + if (W_ERROR_EQUAL(result, WERR_PRINTER_ALREADY_EXISTS)) { + struct policy_handle printer_handle; + + if (existing_printer_deleted) { + torture_fail(tctx, "already deleted printer still existing?"); + } + + torture_assert(tctx, call_OpenPrinterEx(tctx, p, printername, NULL, &printer_handle), + "failed to open printer handle"); + + torture_assert(tctx, test_DeletePrinter(tctx, b, &printer_handle), + "failed to delete printer"); + + torture_assert(tctx, test_ClosePrinter(tctx, b, &printer_handle), + "failed to close server handle"); + + existing_printer_deleted = true; + + goto again; + } + + torture_assert_werr_equal(tctx, result, WERR_UNKNOWN_PORT, + "unexpected result code"); + + info2.portname = portname; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_UNKNOWN_PRINTER_DRIVER, + "unexpected result code"); + + info2.drivername = drivername; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + + /* w2k8r2 allows one to add printer w/o defining printprocessor */ + + if (!W_ERROR_IS_OK(result)) { + torture_assert_werr_equal(tctx, result, WERR_UNKNOWN_PRINTPROCESSOR, + "unexpected result code"); + + info2.printprocessor = "winprint"; + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_ok(tctx, result, + "failed to add printer"); + } + + *handle_p = handle; + + /* we are paranoid, really check if the printer is there now */ + + torture_assert(tctx, test_EnumPrinters_findname(tctx, b, + PRINTER_ENUM_LOCAL, 1, + printername, + &found), + "failed to enum printers"); + torture_assert(tctx, found, "failed to find newly added printer"); + + torture_assert_ntstatus_ok(tctx, ex ? dcerpc_spoolss_AddPrinterEx_r(b, tctx, &rex) : + dcerpc_spoolss_AddPrinter_r(b, tctx, &r), + "failed to add printer"); + result = ex ? rex.out.result : r.out.result; + torture_assert_werr_equal(tctx, result, WERR_PRINTER_ALREADY_EXISTS, + "unexpected result code"); + + return true; +} + +static bool test_printer_info(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + bool ret = true; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping printer info cross tests against samba 3"); + } + + if (!test_PrinterInfo(tctx, b, &t->handle)) { + ret = false; + } + + if (!test_SetPrinter_errors(tctx, b, &t->handle)) { + ret = false; + } + + return ret; +} + +static bool test_EnumPrinterKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key_name, + const char ***array) +{ + struct spoolss_EnumPrinterKey r; + uint32_t needed = 0; + union spoolss_KeyNames key_buffer; + int32_t offered[] = { 0, 1, 2, 3, 4, 5, -1, -2, -3, -4, -5, 256, 512, 1024, 2048 }; + uint32_t _ndr_size; + int i; + + r.in.handle = handle; + r.in.key_name = key_name; + r.out.key_buffer = &key_buffer; + r.out.needed = &needed; + r.out._ndr_size = &_ndr_size; + + for (i=0; i < ARRAY_SIZE(offered); i++) { + + if (offered[i] < 0 && needed) { + if (needed <= 4) { + continue; + } + r.in.offered = needed + offered[i]; + } else { + r.in.offered = offered[i]; + } + + ZERO_STRUCT(key_buffer); + + torture_comment(tctx, "Testing EnumPrinterKey(%s) with %d offered\n", r.in.key_name, r.in.offered); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinterKey_r(b, tctx, &r), + "failed to call EnumPrinterKey"); + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + + torture_assert(tctx, (_ndr_size == r.in.offered/2), + talloc_asprintf(tctx, "EnumPrinterKey size mismatch, _ndr_size %d (expected %d)", + _ndr_size, r.in.offered/2)); + + r.in.offered = needed; + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinterKey_r(b, tctx, &r), + "failed to call EnumPrinterKey"); + } + + if (offered[i] > 0) { + torture_assert_werr_ok(tctx, r.out.result, + "failed to call EnumPrinterKey"); + } + + torture_assert(tctx, (_ndr_size == r.in.offered/2), + talloc_asprintf(tctx, "EnumPrinterKey size mismatch, _ndr_size %d (expected %d)", + _ndr_size, r.in.offered/2)); + + torture_assert(tctx, (*r.out.needed <= r.in.offered), + talloc_asprintf(tctx, "EnumPrinterKey size mismatch: needed %d is not <= offered %d", *r.out.needed, r.in.offered)); + + torture_assert(tctx, (*r.out.needed <= _ndr_size * 2), + talloc_asprintf(tctx, "EnumPrinterKey size mismatch: needed %d is not <= _ndr_size %d * 2", *r.out.needed, _ndr_size)); + + if (key_buffer.string_array) { + uint32_t calc_needed = 0; + int s; + for (s=0; key_buffer.string_array[s]; s++) { + calc_needed += strlen_m_term(key_buffer.string_array[s])*2; + } + if (!key_buffer.string_array[0]) { + calc_needed += 2; + } + calc_needed += 2; + + torture_assert_int_equal(tctx, *r.out.needed, calc_needed, + "EnumPrinterKey unexpected size"); + } + } + + if (array) { + *array = key_buffer.string_array; + } + + return true; +} + +bool test_printer_all_keys(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + const char **key_array = NULL; + int i; + + torture_comment(tctx, "Testing Printer Keys\n"); + + torture_assert(tctx, test_EnumPrinterKey(tctx, b, handle, "", &key_array), + "failed to call test_EnumPrinterKey"); + + for (i=0; key_array && key_array[i]; i++) { + torture_assert(tctx, test_EnumPrinterKey(tctx, b, handle, key_array[i], NULL), + "failed to call test_EnumPrinterKey"); + } + for (i=0; key_array && key_array[i]; i++) { + torture_assert(tctx, test_EnumPrinterDataEx(tctx, b, handle, key_array[i], NULL, NULL), + "failed to call test_EnumPrinterDataEx"); + } + + torture_comment(tctx, "Printer Keys test succeeded\n\n"); + + return true; +} + +static bool test_openprinter_wrap(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *printername = t->info2.printername; + + return test_openprinter(tctx, b, printername); +} + +static bool test_csetprinter(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + const char *printername = talloc_asprintf(tctx, "%s2", t->info2.printername); + const char *drivername = t->added_driver ? t->driver.info8.driver_name : t->info2.drivername; + const char *portname = t->info2.portname; + + union spoolss_PrinterInfo info; + struct policy_handle new_handle, new_handle2; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "Testing c_setprinter\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &t->handle, 0, &info), + "failed to get level 0 printer info"); + torture_comment(tctx, "csetprinter on initial printer handle: %d\n", + info.info0.c_setprinter); + + /* check if c_setprinter on 1st handle increases after a printer has + * been added */ + + torture_assert(tctx, + test_AddPrinter_normal(tctx, p, &new_handle, printername, drivername, portname, NULL, false), + "failed to add new printer"); + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &t->handle, 0, &info), + "failed to get level 0 printer info"); + torture_comment(tctx, "csetprinter on initial printer handle (after add): %d\n", + info.info0.c_setprinter); + + /* check if c_setprinter on new handle increases after a printer has + * been added */ + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &new_handle, 0, &info), + "failed to get level 0 printer info"); + torture_comment(tctx, "csetprinter on created handle: %d\n", + info.info0.c_setprinter); + + /* open the new printer and check if c_setprinter increases */ + + torture_assert(tctx, + call_OpenPrinterEx(tctx, p, printername, NULL, &new_handle2), + "failed to open created printer"); + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &new_handle2, 0, &info), + "failed to get level 0 printer info"); + torture_comment(tctx, "csetprinter on new handle (after openprinter): %d\n", + info.info0.c_setprinter); + + /* cleanup */ + + torture_assert(tctx, + test_ClosePrinter(tctx, b, &new_handle2), + "failed to close printer"); + torture_assert(tctx, + test_DeletePrinter(tctx, b, &new_handle), + "failed to delete new printer"); + + return true; +} + +static bool compose_local_driver_directory(struct torture_context *tctx, + const char *environment, + const char *local_dir, + const char **path) +{ + char *p; + + p = strrchr(local_dir, '/'); + if (!p) { + return NULL; + } + p++; + + if (strequal(environment, SPOOLSS_ARCHITECTURE_x64)) { + if (!strequal(p, "x64")) { + *path = talloc_asprintf(tctx, "%s/x64", local_dir); + } + } else if (strequal(environment, SPOOLSS_ARCHITECTURE_NT_X86)) { + if (!strequal(p, "i386")) { + *path = talloc_asprintf(tctx, "%s/i386", local_dir); + } + } else { + torture_assert(tctx, "unknown environment: '%s'\n", environment); + } + + return true; +} + +#if 0 +static struct spoolss_DeviceMode *torture_devicemode(TALLOC_CTX *mem_ctx, + const char *devicename) +{ + struct spoolss_DeviceMode *r; + + r = talloc_zero(mem_ctx, struct spoolss_DeviceMode); + if (r == NULL) { + return NULL; + } + + r->devicename = talloc_strdup(r, devicename); + r->specversion = DMSPEC_NT4_AND_ABOVE; + r->driverversion = 0x0600; + r->size = 0x00dc; + r->__driverextra_length = 0; + r->fields = DEVMODE_FORMNAME | + DEVMODE_TTOPTION | + DEVMODE_PRINTQUALITY | + DEVMODE_DEFAULTSOURCE | + DEVMODE_COPIES | + DEVMODE_SCALE | + DEVMODE_PAPERSIZE | + DEVMODE_ORIENTATION; + r->orientation = DMORIENT_PORTRAIT; + r->papersize = DMPAPER_LETTER; + r->paperlength = 0; + r->paperwidth = 0; + r->scale = 100; + r->copies = 55; + r->defaultsource = DMBIN_FORMSOURCE; + r->printquality = DMRES_HIGH; + r->color = DMRES_MONOCHROME; + r->duplex = DMDUP_SIMPLEX; + r->yresolution = 0; + r->ttoption = DMTT_SUBDEV; + r->collate = DMCOLLATE_FALSE; + r->formname = talloc_strdup(r, "Letter"); + + return r; +} +#endif + +static bool test_architecture_buffer(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + struct spoolss_OpenPrinterEx r; + struct spoolss_UserLevel1 u1; + struct policy_handle handle; + uint32_t architectures[] = { + PROCESSOR_ARCHITECTURE_INTEL, + PROCESSOR_ARCHITECTURE_IA64, + PROCESSOR_ARCHITECTURE_AMD64 + }; + uint32_t needed[3]; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i < ARRAY_SIZE(architectures); i++) { + + torture_comment(tctx, "Testing OpenPrinterEx with architecture %d\n", architectures[i]); + + u1.size = 0; + u1.client = NULL; + u1.user = NULL; + u1.build = 0; + u1.major = 3; + u1.minor = 0; + u1.processor = architectures[i]; + + r.in.printername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.datatype = NULL; + r.in.devmode_ctr.devmode= NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.userlevel_ctr.level = 1; + r.in.userlevel_ctr.user_info.level1 = &u1; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &r), ""); + torture_assert_werr_ok(tctx, r.out.result, ""); + + { + struct spoolss_EnumPrinters e; + uint32_t count; + union spoolss_PrinterInfo *info; + + e.in.flags = PRINTER_ENUM_LOCAL; + e.in.server = NULL; + e.in.level = 2; + e.in.buffer = NULL; + e.in.offered = 0; + e.out.count = &count; + e.out.info = &info; + e.out.needed = &needed[i]; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_EnumPrinters_r(b, tctx, &e), ""); +#if 0 + torture_comment(tctx, "needed was %d\n", needed[i]); +#endif + } + + torture_assert(tctx, test_ClosePrinter(tctx, b, &handle), ""); + } + + for (i=1; i < ARRAY_SIZE(architectures); i++) { + if (needed[i-1] != needed[i]) { + torture_fail(tctx, + talloc_asprintf(tctx, "needed size %d for architecture %d != needed size %d for architecture %d\n", + needed[i-1], architectures[i-1], needed[i], architectures[i])); + } + } + + return true; +} + +static bool test_get_core_printer_drivers_arch_guid(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *architecture, + const char *guid_str, + const char **package_id) +{ + struct spoolss_GetCorePrinterDrivers r; + struct spoolss_CorePrinterDriver core_printer_drivers; + DATA_BLOB blob = data_blob_talloc_zero(tctx, 2); + const char **s; + struct dcerpc_binding_handle *b = p->binding_handle; + struct GUID guid; + + s = talloc_zero_array(tctx, const char *, 2); + + r.in.servername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.architecture = "foobar"; + r.in.core_driver_size = 0; + r.in.core_driver_dependencies = (uint16_t *)blob.data; + r.in.core_printer_driver_count = 0; + r.out.core_printer_drivers = &core_printer_drivers; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetCorePrinterDrivers_r(b, tctx, &r), + "spoolss_GetCorePrinterDrivers failed"); + torture_assert_hresult_equal(tctx, r.out.result, HRES_E_INVALIDARG, + "spoolss_GetCorePrinterDrivers failed"); + + guid = GUID_random(); + s[0] = GUID_string2(tctx, &guid); + + torture_assert(tctx, + push_reg_multi_sz(tctx, &blob, s), + "push_reg_multi_sz failed"); + + r.in.core_driver_size = blob.length/2; + r.in.core_driver_dependencies = (uint16_t *)blob.data; + r.in.core_printer_driver_count = 1; + r.out.core_printer_drivers = talloc_zero_array(tctx, struct spoolss_CorePrinterDriver, r.in.core_printer_driver_count); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetCorePrinterDrivers_r(b, tctx, &r), + "spoolss_GetCorePrinterDrivers failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INVALID_ENVIRONMENT, + "spoolss_GetCorePrinterDrivers failed"); + + r.in.architecture = architecture; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetCorePrinterDrivers_r(b, tctx, &r), + "spoolss_GetCorePrinterDrivers failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_NOT_FOUND, + "spoolss_GetCorePrinterDrivers failed"); + + s[0] = talloc_strdup(s, guid_str); + + torture_assert(tctx, + push_reg_multi_sz(tctx, &blob, s), + "push_reg_multi_sz failed"); + + r.in.core_driver_size = blob.length/2; + r.in.core_driver_dependencies = (uint16_t *)blob.data; + r.in.core_printer_driver_count = 1; + r.out.core_printer_drivers = talloc_zero_array(tctx, struct spoolss_CorePrinterDriver, r.in.core_printer_driver_count); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetCorePrinterDrivers_r(b, tctx, &r), + "spoolss_GetCorePrinterDrivers failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "spoolss_GetCorePrinterDrivers failed"); + + if (package_id) { + *package_id = r.out.core_printer_drivers[0].szPackageID; + } + + return true; +} + +static bool test_get_core_printer_drivers(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + const char *architectures[] = { + SPOOLSS_ARCHITECTURE_NT_X86, + SPOOLSS_ARCHITECTURE_x64 + }; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + + for (i=0; i < ARRAY_SIZE(architectures); i++) { + + torture_comment(tctx, "Testing GetCorePrinterDrivers(\"%s\",\"%s\")\n", + architectures[i], + SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV); + + torture_assert(tctx, + test_get_core_printer_drivers_arch_guid(tctx, p, + architectures[i], + SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV, + NULL), + ""); + } + + return true; +} + +static bool test_get_printer_driver_package_path(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + const char *architectures[] = { + SPOOLSS_ARCHITECTURE_NT_X86, + SPOOLSS_ARCHITECTURE_x64 + }; + int i; + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i < ARRAY_SIZE(architectures); i++) { + struct spoolss_GetPrinterDriverPackagePath r; + uint32_t required = 0; + const char *package_id = NULL; + + test_get_core_printer_drivers_arch_guid(tctx, p, + architectures[i], + SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV, + &package_id), + + torture_comment(tctx, "Testing GetPrinterDriverPackagePath(\"%s\",\"%s\")\n", + architectures[i], package_id); + + r.in.servername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.architecture = "foobar"; + r.in.language = NULL; + r.in.package_id = ""; + r.in.driver_package_cab_size = 0; + r.in.driver_package_cab = NULL; + + r.out.required = &required; + r.out.driver_package_cab = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INVALID_ENVIRONMENT, + "spoolss_GetPrinterDriverPackagePath failed"); + + r.in.architecture = architectures[i]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_FILE_NOT_FOUND, + "spoolss_GetPrinterDriverPackagePath failed"); + + r.in.package_id = package_id; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "spoolss_GetPrinterDriverPackagePath failed"); + + r.in.driver_package_cab_size = required; + r.in.driver_package_cab = talloc_zero_array(tctx, char, required); + r.out.driver_package_cab = talloc_zero_array(tctx, char, required); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "spoolss_GetPrinterDriverPackagePath failed"); + + r.in.servername = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_werr_equal(tctx, + W_ERROR(WIN32_FROM_HRESULT(r.out.result)), WERR_INSUFFICIENT_BUFFER, + "spoolss_GetPrinterDriverPackagePath failed"); + + r.in.driver_package_cab_size = required; + r.in.driver_package_cab = talloc_zero_array(tctx, char, required); + r.out.driver_package_cab = talloc_zero_array(tctx, char, required); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverPackagePath_r(b, tctx, &r), + "spoolss_GetPrinterDriverPackagePath failed"); + torture_assert_hresult_ok(tctx, r.out.result, + "spoolss_GetPrinterDriverPackagePath failed"); + + } + + return true; +} + +static bool test_get_printer_printserverhandle(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t levels[] = {0, 1, 2, /* 3,*/ 4, 5, 6, 7, 8}; + int i; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + torture_assert(tctx, + test_GetPrinter_level_exp(tctx, b, &ctx->server_handle, + levels[i], WERR_INVALID_LEVEL, + NULL), + "failed to call GetPrinter"); + } + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &ctx->server_handle, 3, NULL), + "failed to call GetPrinter"); + + return true; +} + +#define TEST_SID "S-1-5-21-1234567890-1234567890-1234567890-500" + +static bool test_set_printer_printserverhandle(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + union spoolss_PrinterInfo info; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo3 info3; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct security_descriptor *sd; + struct security_ace *ace; + struct dom_sid sid; + int i; + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &ctx->server_handle, 3, &info), + "failed to call GetPrinter"); + + secdesc_ctr.sd = info.info3.secdesc; + secdesc_ctr.sd->owner_sid = NULL; + secdesc_ctr.sd->group_sid = NULL; + + sd = security_descriptor_copy(tctx, secdesc_ctr.sd); + if (sd == NULL) { + return false; + } + + ace = security_ace_create(tctx, + TEST_SID, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_REQUIRED, + SEC_ACE_FLAG_CONTAINER_INHERIT); + torture_assert(tctx, ace, "failed to create ace"); + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_add(sd, ace), + "failed to add ace"); + + secdesc_ctr.sd = sd; + + info3.sec_desc_ptr = 0; + + info_ctr.level = 3; + info_ctr.info.info3 = &info3; + + ZERO_STRUCT(devmode_ctr); + + torture_assert(tctx, + test_SetPrinter(tctx, b, &ctx->server_handle, &info_ctr, + &devmode_ctr, &secdesc_ctr, 0), + "failed to call SetPrinter"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &ctx->server_handle, 3, &info), + "failed to call GetPrinter"); + + for (i = 0; i < info.info3.secdesc->dacl->num_aces; i++) { + if (security_ace_equal(&info.info3.secdesc->dacl->aces[i], ace)) { + break; + } + } + + if (i == info.info3.secdesc->dacl->num_aces) { + torture_fail(tctx, "ace not present"); + } + + torture_assert(tctx, + dom_sid_parse(TEST_SID, &sid), + "failed to parse sid"); + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_del(info.info3.secdesc, &sid), + "failed to remove ace from sd"); + + secdesc_ctr.sd = info.info3.secdesc; + + torture_assert(tctx, + test_SetPrinter(tctx, b, &ctx->server_handle, &info_ctr, + &devmode_ctr, &secdesc_ctr, 0), + "failed to call SetPrinter"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &ctx->server_handle, 3, &info), + "failed to call GetPrinter"); + + for (i = 0; i < info.info3.secdesc->dacl->num_aces; i++) { + if (security_ace_equal(&info.info3.secdesc->dacl->aces[i], ace)) { + torture_fail(tctx, "ace still present"); + } + } + + return true; +} + + +static bool test_PrintServer_Forms_Winreg(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_Forms_winreg(tctx, b, &ctx->server_handle, true, NULL); +} + +static bool test_PrintServer_Forms(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_Forms(tctx, b, &ctx->server_handle, true, NULL, NULL, NULL); +} + +static bool test_PrintServer_EnumForms(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *ctx = + talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = ctx->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + return test_EnumForms_all(tctx, b, &ctx->server_handle, true); +} + +static bool torture_rpc_spoolss_setup_common(struct torture_context *tctx, struct test_spoolss_context *t) +{ + NTSTATUS status; + + status = torture_rpc_connection(tctx, &t->spoolss_pipe, &ndr_table_spoolss); + + torture_assert_ntstatus_ok(tctx, status, "Error connecting to server"); + + torture_assert(tctx, + test_OpenPrinter_server(tctx, t->spoolss_pipe, &t->server_handle), + "failed to open printserver"); + torture_assert(tctx, + test_get_environment(tctx, t->spoolss_pipe->binding_handle, &t->server_handle, &t->environment), + "failed to get environment"); + + return true; +} + +static bool torture_rpc_spoolss_setup(struct torture_context *tctx, void **data) +{ + struct test_spoolss_context *t; + + *data = t = talloc_zero(tctx, struct test_spoolss_context); + + return torture_rpc_spoolss_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_teardown_common(struct torture_context *tctx, struct test_spoolss_context *t) +{ + test_ClosePrinter(tctx, t->spoolss_pipe->binding_handle, &t->server_handle); + + return true; +} + +static bool torture_rpc_spoolss_teardown(struct torture_context *tctx, void *data) +{ + struct test_spoolss_context *t = talloc_get_type(data, struct test_spoolss_context); + bool ret; + + ret = torture_rpc_spoolss_teardown_common(tctx, t); + talloc_free(t); + + return ret; +} + +static bool torture_rpc_spoolss_printer_setup_common(struct torture_context *tctx, struct torture_printer_context *t) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + const char *server_name_slash; + const char *driver_name; + const char *printer_name; + const char *port_name; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &t->spoolss_pipe, &ndr_table_spoolss), + "Error connecting to server"); + + p = t->spoolss_pipe; + b = p->binding_handle; + server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + t->driver.info8.version = SPOOLSS_DRIVER_VERSION_200X; + t->driver.info8.driver_name = TORTURE_DRIVER; + t->driver.info8.driver_path = "pscript5.dll"; + t->driver.info8.data_file = "cups6.ppd"; + t->driver.info8.config_file = "ps5ui.dll"; + t->driver.info8.help_file = "pscript.hlp"; + t->driver.info8.default_datatype = "RAW"; + t->driver.info8.dependent_files = talloc_zero(t, struct spoolss_StringArray); + t->driver.info8.dependent_files->string = talloc_zero_array(t, const char *, 8 + 1); + t->driver.info8.dependent_files->string[0] = "pscript5.dll"; + t->driver.info8.dependent_files->string[1] = "cups6.ppd"; + t->driver.info8.dependent_files->string[2] = "ps5ui.dll"; + t->driver.info8.dependent_files->string[3] = "pscript.hlp"; + t->driver.info8.dependent_files->string[4] = "pscript.ntf"; + t->driver.info8.dependent_files->string[5] = "cups6.ini"; + t->driver.info8.dependent_files->string[6] = "cupsps6.dll"; + t->driver.info8.dependent_files->string[7] = "cupsui6.dll"; + + t->driver.local.driver_directory= "/usr/share/cups/drivers"; + + t->info2.portname = "LPT1:"; + + printer_name = t->info2.printername; + port_name = t->info2.portname; + + torture_assert(tctx, + fillup_printserver_info(tctx, p, &t->driver), + "failed to fillup printserver info"); + + t->driver.info8.architecture = talloc_strdup(t, t->driver.remote.environment); + + torture_assert(tctx, + compose_local_driver_directory(tctx, t->driver.remote.environment, + t->driver.local.driver_directory, + &t->driver.local.driver_directory), + "failed to compose local driver directory"); + + t->info2.drivername = "Microsoft XPS Document Writer"; + + if (test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, t->driver.remote.environment, 3, t->info2.drivername, NULL)) { + torture_comment(tctx, "driver '%s' (architecture: %s, version: 3) is present on server\n", + t->info2.drivername, t->driver.remote.environment); + t->have_driver = true; + goto try_add; + } + + torture_comment(tctx, "driver '%s' (architecture: %s, version: 3) does not exist on the server\n", + t->info2.drivername, t->driver.remote.environment); + + t->info2.drivername = "Microsoft XPS Document Writer v4"; + + if (test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, t->driver.remote.environment, 3, t->info2.drivername, NULL)) { + torture_comment(tctx, "driver '%s' (architecture: %s, version: 4) is present on server\n", + t->info2.drivername, t->driver.remote.environment); + t->have_driver = true; + goto try_add; + } + + torture_comment(tctx, "trying to upload own driver\n"); + + if (!directory_exist(t->driver.local.driver_directory)) { + torture_warning(tctx, "no local driver is available!"); + t->have_driver = false; + goto try_add; + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), &t->driver), + "failed to upload printer driver"); + + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &t->driver.info8, 0, false, NULL), + "failed to add driver"); + + t->added_driver = true; + t->have_driver = true; + + try_add: + driver_name = t->added_driver ? t->driver.info8.driver_name : t->info2.drivername; + + if (t->wellknown) { + torture_assert(tctx, + test_AddPrinter_wellknown(tctx, p, printer_name, t->ex), + "failed to add wellknown printer"); + } else { + torture_assert(tctx, + test_AddPrinter_normal(tctx, p, &t->handle, printer_name, driver_name, port_name, t->devmode, t->ex), + "failed to add printer"); + } + + return true; +} + +static bool torture_rpc_spoolss_printer_setup(struct torture_context *tctx, void **data) +{ + struct torture_printer_context *t; + + *data = t = talloc_zero(tctx, struct torture_printer_context); + + t->ex = false; + t->wellknown = false; + t->info2.printername = TORTURE_PRINTER; + t->devmode = NULL; + + return torture_rpc_spoolss_printer_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_printerex_setup(struct torture_context *tctx, void **data) +{ + struct torture_printer_context *t; + + *data = t = talloc_zero(tctx, struct torture_printer_context); + + t->ex = true; + t->wellknown = false; + t->info2.printername = TORTURE_PRINTER_EX; + t->devmode = NULL; + + return torture_rpc_spoolss_printer_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_printerwkn_setup(struct torture_context *tctx, void **data) +{ + struct torture_printer_context *t; + + *data = t = talloc_zero(tctx, struct torture_printer_context); + + t->ex = false; + t->wellknown = true; + t->info2.printername = TORTURE_WELLKNOWN_PRINTER; + t->devmode = NULL; + + /* FIXME */ + if (t->wellknown) { + torture_skip(tctx, "skipping AddPrinter level 1"); + } + + return torture_rpc_spoolss_printer_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_printerexwkn_setup(struct torture_context *tctx, void **data) +{ + struct torture_printer_context *t; + + *data = t = talloc_zero(tctx, struct torture_printer_context); + + t->ex = true; + t->wellknown = true; + t->info2.printername = TORTURE_WELLKNOWN_PRINTER_EX; + t->devmode = NULL; + + /* FIXME */ + if (t->wellknown) { + torture_skip(tctx, "skipping AddPrinterEx level 1"); + } + + return torture_rpc_spoolss_printer_setup_common(tctx, t); +} + +#if 0 +static bool torture_rpc_spoolss_printerdm_setup(struct torture_context *tctx, void **data) +{ + struct torture_printer_context *t; + + *data = t = talloc_zero(tctx, struct torture_printer_context); + + t->ex = true; + t->wellknown = false; + t->info2.printername = TORTURE_PRINTER_EX; + t->devmode = torture_devicemode(t, TORTURE_PRINTER_EX); + + return torture_rpc_spoolss_printer_setup_common(tctx, t); +} +#endif + +static bool test_DeletePrinterDriverEx_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server, + const char *driver, + const char *environment, + uint32_t delete_flags, + uint32_t version, + WERROR expected_result); + +static bool torture_rpc_spoolss_printer_teardown_common(struct torture_context *tctx, struct torture_printer_context *t) +{ + bool found = false; + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = NULL; + const char *server_name_slash; + bool ok = true; + + if (p == NULL) { + return true; + } + b = p->binding_handle; + + server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + if (!t->wellknown) { + const char *printer_name = t->info2.printername; + + torture_assert_goto(tctx, + test_DeletePrinter(tctx, b, &t->handle), + ok, + remove_driver, + "failed to delete printer"); + + torture_assert_goto(tctx, + test_EnumPrinters_findname(tctx, b, PRINTER_ENUM_LOCAL, 1, + printer_name, &found), + ok, + remove_driver, + "failed to enumerate printers"); + + torture_assert_goto(tctx, + !found, + ok, + remove_driver, + "deleted printer still there"); + } + + +remove_driver: + if (t->added_driver) { + ok = remove_printer_driver(tctx, + dcerpc_server_name(p), + &t->driver); + if (!ok) { + torture_warning(tctx, + "failed to remove printer driver\n"); + } + + ok = test_DeletePrinterDriverEx_exp(tctx, b, + server_name_slash, + t->driver.info8.driver_name, + t->driver.info8.architecture, + DPD_DELETE_ALL_FILES, + t->driver.info8.version, + WERR_OK); + if (!ok) { + torture_warning(tctx, + "failed to delete printer driver via " + "spoolss\n"); + } + } + + return ok; +} + +static bool torture_rpc_spoolss_printer_teardown(struct torture_context *tctx, void *data) +{ + struct torture_printer_context *t = talloc_get_type(data, struct torture_printer_context); + bool ret; + + ret = torture_rpc_spoolss_printer_teardown_common(tctx, t); + talloc_free(t); + + return ret; +} + +static bool test_print_test(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_PausePrinter(tctx, b, &t->handle), + "failed to pause printer"); + + torture_assert(tctx, + test_DoPrintTest(tctx, b, &t->handle), + "failed to do print test"); + + torture_assert(tctx, + test_ResumePrinter(tctx, b, &t->handle), + "failed to resume printer"); + + return true; +} + +static bool test_print_test_extended(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + bool ret = true; + + torture_assert(tctx, + test_PausePrinter(tctx, b, &t->handle), + "failed to pause printer"); + + ret = test_DoPrintTest_extended(tctx, b, &t->handle); + if (ret == false) { + torture_comment(tctx, "WARNING! failed to do extended print test\n"); + if (torture_setting_bool(tctx, "samba3", false)) { + torture_comment(tctx, "non-critical for samba3\n"); + ret = true; + tctx->last_result = TORTURE_SKIP; + } + } + + torture_assert(tctx, + test_ResumePrinter(tctx, b, &t->handle), + "failed to resume printer"); + + return ret; +} + +static bool test_print_test_properties(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skip printer job property tests against samba"); + } + + torture_assert(tctx, + test_PausePrinter(tctx, b, &t->handle), + "failed to pause printer"); + + torture_assert(tctx, + test_DoPrintTest_properties(tctx, b, &t->handle), + "failed to test print job properties"); + + torture_assert(tctx, + test_ResumePrinter(tctx, b, &t->handle), + "failed to resume printer"); + + return true; +} + +/* use smbd file IO to spool a print job */ +static bool test_print_test_smbd(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + NTSTATUS status; + uint32_t count; + union spoolss_JobInfo *info = NULL; + int i; + + struct smb2_tree *tree; + struct smb2_handle job_h; + struct smbcli_options options; + TALLOC_CTX *mem_ctx = talloc_new(tctx); + /* + * Do not test against the dynamically added printers, printing via + * smbd means that a different spoolss process may handle the + * OpenPrinter request to the one that handled the AddPrinter request. + * This currently leads to an ugly race condition where one process + * sees the new printer and one doesn't. + */ + const char *share = TORTURE_PRINTER_STATIC1; + + torture_comment(tctx, "Testing smbd job spooling\n"); + lpcfg_smbcli_options(tctx->lp_ctx, &options); + + status = smb2_connect(mem_ctx, + torture_setting_string(tctx, "host", NULL), + lpcfg_smb_ports(tctx->lp_ctx), + share, + lpcfg_resolve_context(tctx->lp_ctx), + samba_cmdline_get_creds(), + &tree, + tctx->ev, + &options, + lpcfg_socket_options(tctx->lp_ctx), + lpcfg_gensec_settings(tctx, tctx->lp_ctx)); + if (!NT_STATUS_IS_OK(status)) { + printf("Failed to connect to SMB2 printer %s - %s\n", + share, nt_errstr(status)); + return false; + } + + status = torture_smb2_testfile(tree, "smbd_spooler_job", &job_h); + torture_assert_ntstatus_ok(tctx, status, "smbd spool job create"); + + status = smb2_util_write(tree, job_h, "exciting print job data", 0, + sizeof("exciting print job data")); + torture_assert_ntstatus_ok(tctx, status, "smbd spool job write"); + + /* check back end spoolss job was created */ + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK, + &count, &info), + "EnumJobs level 1 failed"); + + for (i = 0; i < count; i++) { + if (!strcmp(info[i].info1.document_name, "smbd_spooler_job")) { + break; + } + } + torture_assert(tctx, (i != count), "smbd_spooler_job not found"); + + status = smb2_util_close(tree, job_h); + torture_assert_ntstatus_ok(tctx, status, "smbd spool job close"); + + /* disconnect from printer share */ + talloc_free(mem_ctx); + + return true; +} + +static bool test_print_test_purge(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, + struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t num_jobs = 8; + uint32_t *job_ids; + int i; + bool ret = true; + uint32_t count; + union spoolss_JobInfo *info; + + torture_assert(tctx, + test_PausePrinter(tctx, b, &t->handle), + "failed to pause printer"); + + job_ids = talloc_zero_array(tctx, uint32_t, num_jobs); + for (i=0; i < num_jobs; i++) { + ret = test_DoPrintTest_add_one_job(tctx, b, &t->handle, + "TorturePrintJob", + &job_ids[i]); + torture_assert(tctx, ret, "failed to add print job"); + } + + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK, + &count, &info), + "EnumJobs level 1 failed"); + + torture_assert_int_equal(tctx, count, num_jobs, + "unexpected number of jobs in queue"); + + torture_assert(tctx, + test_printer_purge(tctx, b, &t->handle), + "failed to purge printer"); + + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK, + &count, &info), + "EnumJobs level 1 failed"); + + torture_assert_int_equal(tctx, count, 0, + "unexpected number of jobs in queue"); + + torture_assert(tctx, + test_ResumePrinter(tctx, b, &t->handle), + "failed to resume printer"); + + return true; +} + +static bool test_printer_sd(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_PrinterInfo_SD(tctx, b, &t->handle), + "failed to test security descriptors"); + + return true; +} + +static bool test_printer_dm(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_PrinterInfo_DevMode(tctx, p, &t->handle, t->info2.printername, t->devmode), + "failed to test devicemodes"); + + return true; +} + +static bool test_printer_info_winreg(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_PrinterInfo_winreg(tctx, p, &t->handle, t->info2.printername), + "failed to test printer info winreg"); + + return true; +} + +static bool test_printserver_info_winreg(struct torture_context *tctx, + void *private_data) +{ + struct test_spoolss_context *t = + (struct test_spoolss_context *)talloc_get_type_abort(private_data, struct test_spoolss_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_PrintserverInfo_winreg(tctx, p, &t->server_handle), + "failed to test printserver info winreg"); + + return true; +} + + +static bool test_printer_change_id(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_ChangeID(tctx, p, &t->handle), + "failed to test change id"); + + return true; +} + +static bool test_printer_keys(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_printer_all_keys(tctx, b, &t->handle), + "failed to test printer keys"); + + return true; +} + +static bool test_printer_data_consistency(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_EnumPrinterData_consistency(tctx, p, &t->handle), + "failed to test printer data consistency"); + + return true; +} + +static bool test_printer_data_keys(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_SetPrinterDataEx_keys(tctx, p, &t->handle), + "failed to test printer data keys"); + + return true; +} + +static bool test_printer_data_values(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_SetPrinterDataEx_values(tctx, p, &t->handle), + "failed to test printer data values"); + + return true; +} + +static bool test_printer_data_set(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_SetPrinterDataEx_matrix(tctx, p, &t->handle, t->info2.printername, NULL, NULL), + "failed to test printer data set"); + + return true; +} + +static bool test_printer_data_winreg(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_PrinterData_winreg(tctx, p, &t->handle, t->info2.printername), + "failed to test printer data winreg"); + + return true; +} + +static bool test_printer_data_dsspooler(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + + torture_assert(tctx, + test_PrinterData_DsSpooler(tctx, p, &t->handle, t->info2.printername), + "failed to test printer data winreg dsspooler"); + + return true; +} + +static bool test_printer_ic(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + talloc_get_type_abort(private_data, + struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle gdi_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skip printer information context tests against samba"); + } + + { + struct spoolss_CreatePrinterIC r; + struct spoolss_DevmodeContainer devmode_ctr; + + ZERO_STRUCT(devmode_ctr); + + r.in.handle = &t->handle; + r.in.devmode_ctr = &devmode_ctr; + r.out.gdi_handle = &gdi_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_CreatePrinterIC_r(b, tctx, &r), + "CreatePrinterIC failed"); + torture_assert_werr_ok(tctx, r.out.result, + "CreatePrinterIC failed"); + } + + { + struct spoolss_PlayGDIScriptOnPrinterIC r; + DATA_BLOB in,out; + int i; + uint32_t num_fonts = 0; + + in = data_blob_string_const(""); + + r.in.gdi_handle = &gdi_handle; + r.in.pIn = in.data; + r.in.cIn = in.length; + r.in.ul = 0; + + for (i = 0; i < 4; i++) { + + out = data_blob_talloc_zero(tctx, i); + + r.in.cOut = out.length; + r.out.pOut = out.data; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r), + "PlayGDIScriptOnPrinterIC failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_ENOUGH_MEMORY, + "PlayGDIScriptOnPrinterIC failed"); + } + + out = data_blob_talloc_zero(tctx, 4); + + r.in.cOut = out.length; + r.out.pOut = out.data; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r), + "PlayGDIScriptOnPrinterIC failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_OK, + "PlayGDIScriptOnPrinterIC failed"); + + /* now we should have the required length, so retry with a + * buffer which is large enough to carry all font ids */ + + num_fonts = IVAL(r.out.pOut, 0); + + torture_comment(tctx, "PlayGDIScriptOnPrinterIC gave font count of %d\n", num_fonts); + + out = data_blob_talloc_zero(tctx, + num_fonts * sizeof(struct UNIVERSAL_FONT_ID) + 4); + + r.in.cOut = out.length; + r.out.pOut = out.data; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_PlayGDIScriptOnPrinterIC_r(b, tctx, &r), + "PlayGDIScriptOnPrinterIC failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_OK, + "PlayGDIScriptOnPrinterIC failed"); + + } + + { + struct spoolss_DeletePrinterIC r; + + r.in.gdi_handle = &gdi_handle; + r.out.gdi_handle = &gdi_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrinterIC_r(b, tctx, &r), + "DeletePrinterIC failed"); + torture_assert_werr_ok(tctx, r.out.result, + "DeletePrinterIC failed"); + + } + + return true; +} + +static bool test_printer_bidi(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + talloc_get_type_abort(private_data, + struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_SendRecvBidiData r; + struct RPC_BIDI_REQUEST_CONTAINER bidi_req; + struct RPC_BIDI_RESPONSE_CONTAINER *bidi_rep = NULL; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skip printer bidirectional tests against samba"); + } + + ZERO_STRUCT(bidi_req); + + r.in.hPrinter = t->handle; + r.in.pAction = "foobar"; + r.in.pReqData = &bidi_req; + r.out.ppRespData = &bidi_rep; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_SendRecvBidiData_r(b, tctx, &r), + "SendRecvBidiData failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "SendRecvBidiData failed"); + + if (!(t->info2.attributes & PRINTER_ATTRIBUTE_ENABLE_BIDI)) { + torture_skip(tctx, "skipping further tests as printer is not BIDI enabled"); + } + + r.in.pAction = BIDI_ACTION_ENUM_SCHEMA; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_SendRecvBidiData_r(b, tctx, &r), + "SendRecvBidiData failed"); + torture_assert_werr_ok(tctx, r.out.result, + "SendRecvBidiData failed"); + + return true; +} + +static bool test_printer_set_publish(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct spoolss_SetPrinterInfo7 info7; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info7.guid = ""; + info7.action = DSPRINT_PUBLISH; + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + info_ctr.level = 7; + info_ctr.info.info7 = &info7; + + torture_assert(tctx, + test_SetPrinter(tctx, b, handle, &info_ctr, + &devmode_ctr, &secdesc_ctr, 0), ""); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 2, &info), + ""); + torture_assert(tctx, + (info.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED), + "info2 publish flag not set"); + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 7, &info), + ""); + if (info.info7.action & DSPRINT_PENDING) { + torture_comment(tctx, "publish is pending\n"); + torture_assert_int_equal(tctx, + info.info7.action, + (DSPRINT_PENDING | DSPRINT_PUBLISH), + "info7 publish flag not set"); + } else { + struct GUID guid; + char *ref_guid; + torture_assert_int_equal(tctx, + info.info7.action, + DSPRINT_PUBLISH, + "info7 publish flag not set"); + + /* GUID_from_string is able to parse both plain and + * curly-braced guids */ + torture_assert_ntstatus_ok(tctx, + GUID_from_string(info.info7.guid, + &guid), + "invalid published printer GUID"); + + /* Build reference GUID string */ + ref_guid = GUID_string2(tctx, &guid); + torture_assert_not_null(tctx, ref_guid, "ENOMEM"); + ref_guid = talloc_strdup_upper(tctx, ref_guid); + torture_assert_not_null(tctx, ref_guid, "ENOMEM"); + torture_assert_str_equal(tctx, info.info7.guid, ref_guid, + "invalid GUID format"); + } + + return true; +} + +static bool test_printer_set_unpublish(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct spoolss_SetPrinterInfo7 info7; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + + info7.action = DSPRINT_UNPUBLISH; + info7.guid = ""; + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + info_ctr.level = 7; + info_ctr.info.info7 = &info7; + + torture_assert(tctx, + test_SetPrinter(tctx, b, handle, &info_ctr, + &devmode_ctr, &secdesc_ctr, 0), ""); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 2, &info), + ""); + torture_assert(tctx, + !(info.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED), + "info2 publish flag still set"); + torture_assert(tctx, + test_GetPrinter_level(tctx, b, handle, 7, &info), + ""); + + if (info.info7.action & DSPRINT_PENDING) { + struct GUID guid; + torture_comment(tctx, "unpublish is pending\n"); + torture_assert_int_equal(tctx, + info.info7.action, + (DSPRINT_PENDING | DSPRINT_UNPUBLISH), + "info7 unpublish flag not set"); + torture_assert_ntstatus_ok(tctx, + GUID_from_string(info.info7.guid, + &guid), + "invalid printer GUID"); + } else { + torture_assert_int_equal(tctx, + info.info7.action, DSPRINT_UNPUBLISH, + "info7 unpublish flag not set"); + } + + return true; +} + +static bool test_printer_publish_toggle(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + talloc_get_type_abort(private_data, + struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle *handle = &t->handle; + union spoolss_PrinterInfo info7; + union spoolss_PrinterInfo info2; + + /* check publish status via level 7 and level 2 */ + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 7, &info7), + ""); + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info2), + ""); + + if (info2.info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED) { + torture_assert_int_equal(tctx, + info7.info7.action, DSPRINT_PUBLISH, + "info7 publish flag not set"); + torture_assert(tctx, test_printer_set_unpublish(tctx, b, handle), ""); + torture_assert(tctx, test_printer_set_publish(tctx, b, handle), ""); + } else { + torture_assert_int_equal(tctx, + info7.info7.action, DSPRINT_UNPUBLISH, + "info7 unpublish flag not set"); + torture_assert(tctx, test_printer_set_publish(tctx, b, handle), ""); + torture_assert(tctx, test_printer_set_unpublish(tctx, b, handle), ""); + } + + return true; +} + +static bool test_driver_info_winreg(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + const char *driver_name = t->added_driver ? t->driver.info8.driver_name : t->info2.drivername; + + if (!t->have_driver) { + torture_skip(tctx, "skipping driver info winreg test as we don't have a driver"); + } + + torture_assert(tctx, + test_DriverInfo_winreg(tctx, p, &t->handle, t->info2.printername, driver_name, t->driver.remote.environment, 3), + "failed to test driver info winreg"); + + return true; +} + +static bool test_print_job_enum(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + bool ret = true; + uint32_t num_jobs = 8; + uint32_t *job_ids; + int i; + union spoolss_JobInfo *info = NULL; + uint32_t count; + + torture_assert(tctx, + test_PausePrinter(tctx, b, &t->handle), + "failed to pause printer"); + + /* purge in case of any jobs from previous tests */ + torture_assert(tctx, + test_printer_purge(tctx, b, &t->handle), + "failed to purge printer"); + + /* enum before jobs, valid level */ + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK, + &count, &info), + "EnumJobs with valid level"); + torture_assert_int_equal(tctx, count, 0, "EnumJobs count"); + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 2, WERR_OK, + &count, &info), + "EnumJobs with valid level"); + torture_assert_int_equal(tctx, count, 0, "EnumJobs count"); + /* enum before jobs, invalid level - expect failure */ + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 100, + WERR_INVALID_LEVEL, + &count, &info), + "EnumJobs with invalid level"); + + job_ids = talloc_zero_array(tctx, uint32_t, num_jobs); + + for (i = 0; i < num_jobs; i++) { + ret = test_DoPrintTest_add_one_job(tctx, b, &t->handle, + "TorturePrintJob", + &job_ids[i]); + torture_assert(tctx, ret, "failed to add print job"); + } + + /* enum after jobs, valid level */ + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 1, WERR_OK, + &count, &info), + "EnumJobs with valid level"); + torture_assert_int_equal(tctx, count, num_jobs, "EnumJobs count"); + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 2, WERR_OK, + &count, &info), + "EnumJobs with valid level"); + torture_assert_int_equal(tctx, count, num_jobs, "EnumJobs count"); + /* enum after jobs, invalid level - expect failure */ + torture_assert(tctx, + test_EnumJobs_args(tctx, b, &t->handle, 100, + WERR_INVALID_LEVEL, + &count, &info), + "EnumJobs with invalid level"); + + for (i = 0; i < num_jobs; i++) { + test_SetJob(tctx, b, &t->handle, job_ids[i], NULL, + SPOOLSS_JOB_CONTROL_DELETE); + } + + torture_assert(tctx, + test_ResumePrinter(tctx, b, &t->handle), + "failed to resume printer"); + + return true; +} + +static bool test_printer_log_jobinfo(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + struct spoolss_BranchOfficeJobDataContainer info; + int i; + + struct spoolss_LogJobInfoForBranchOffice r; + + torture_comment(tctx, "Testing LogJobInfoForBranchOffice\n"); + + info.cJobDataEntries = 0; + info.JobData = NULL; + + r.in.hPrinter = &t->handle; + r.in.pBranchOfficeJobDataContainer = &info; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_LogJobInfoForBranchOffice_r(b, tctx, &r), + "LogJobInfoForBranchOffice failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "LogJobInfoForBranchOffice failed"); + + info.cJobDataEntries = 1; + info.JobData = talloc_zero_array(tctx, struct spoolss_BranchOfficeJobData, info.cJobDataEntries); + + info.JobData[0].eEventType = kLogOfflineFileFull; + info.JobData[0].JobId = 42; + info.JobData[0].JobInfo.LogOfflineFileFull.pMachineName = talloc_strdup(tctx, "mthelena"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_LogJobInfoForBranchOffice_r(b, tctx, &r), + "LogJobInfoForBranchOffice failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_OK, + "LogJobInfoForBranchOffice failed"); + + info.cJobDataEntries = 42; + info.JobData = talloc_zero_array(tctx, struct spoolss_BranchOfficeJobData, info.cJobDataEntries); + + for (i=0; i < info.cJobDataEntries; i++) { + info.JobData[i].eEventType = kLogOfflineFileFull; + info.JobData[i].JobId = i; + info.JobData[i].JobInfo.LogOfflineFileFull.pMachineName = talloc_asprintf(tctx, "torture_%d", i); + } + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_LogJobInfoForBranchOffice_r(b, tctx, &r), + "LogJobInfoForBranchOffice failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_OK, + "LogJobInfoForBranchOffice failed"); + + return true; +} + +static bool test_printer_os_versions(struct torture_context *tctx, + void *private_data) +{ + struct torture_printer_context *t = + (struct torture_printer_context *)talloc_get_type_abort(private_data, struct torture_printer_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + struct dcerpc_binding_handle *b = p->binding_handle; + union spoolss_PrinterInfo info; + DATA_BLOB blob; + uint8_t *data; + uint32_t length; + struct spoolss_OSVersion osversion; + uint8_t os_major, os_minor; + uint16_t os_build; + struct policy_handle server_handle; + + torture_comment(tctx, "Testing OSVersion vs. PRINTER_INFO_STRESS\n"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &t->handle, 0, &info), + "failed to get level 0 printer info"); + + torture_assert(tctx, + test_OpenPrinter_server(tctx, p, &server_handle), + "failed to open printserver"); + + torture_assert(tctx, + test_GetPrinterData_checktype(tctx, b, &server_handle, "OSVersion", + NULL, NULL, &data, &length), + "failed to fetch OSVersion printer data"); + + test_ClosePrinter(tctx, b, &server_handle); + + blob = data_blob_const(data, length); + + torture_assert_ndr_success(tctx, + ndr_pull_struct_blob(&blob, tctx, &osversion, + (ndr_pull_flags_fn_t)ndr_pull_spoolss_OSVersion), + "failed to pull OSVersion"); + + os_major = CVAL(&info.info0.version, 0); + os_minor = CVAL(&info.info0.version, 1); + os_build = SVAL(&info.info0.version, 2); + + torture_assert_int_equal(tctx, os_major, osversion.major, "major"); + torture_assert_int_equal(tctx, os_minor, osversion.minor, "minor"); + torture_assert_int_equal(tctx, os_build, osversion.build, "build"); + + return true; +} + + +void torture_tcase_printer(struct torture_tcase *tcase) +{ + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter_wrap); + torture_tcase_add_simple_test(tcase, "csetprinter", test_csetprinter); + torture_tcase_add_simple_test(tcase, "print_test", test_print_test); + torture_tcase_add_simple_test(tcase, "print_test_extended", test_print_test_extended); + torture_tcase_add_simple_test(tcase, "print_test_smbd", test_print_test_smbd); + torture_tcase_add_simple_test(tcase, "print_test_properties", test_print_test_properties); + torture_tcase_add_simple_test(tcase, "print_test_purge", test_print_test_purge); + torture_tcase_add_simple_test(tcase, "printer_info", test_printer_info); + torture_tcase_add_simple_test(tcase, "sd", test_printer_sd); + torture_tcase_add_simple_test(tcase, "dm", test_printer_dm); + torture_tcase_add_simple_test(tcase, "printer_info_winreg", test_printer_info_winreg); + torture_tcase_add_simple_test(tcase, "change_id", test_printer_change_id); + torture_tcase_add_simple_test(tcase, "keys", test_printer_keys); + torture_tcase_add_simple_test(tcase, "printerdata_consistency", test_printer_data_consistency); + torture_tcase_add_simple_test(tcase, "printerdata_keys", test_printer_data_keys); + torture_tcase_add_simple_test(tcase, "printerdata_values", test_printer_data_values); + torture_tcase_add_simple_test(tcase, "printerdata_set", test_printer_data_set); + torture_tcase_add_simple_test(tcase, "printerdata_winreg", test_printer_data_winreg); + torture_tcase_add_simple_test(tcase, "printerdata_dsspooler", test_printer_data_dsspooler); + torture_tcase_add_simple_test(tcase, "driver_info_winreg", test_driver_info_winreg); + torture_tcase_add_simple_test(tcase, "printer_rename", test_printer_rename); + torture_tcase_add_simple_test(tcase, "printer_ic", test_printer_ic); + torture_tcase_add_simple_test(tcase, "bidi", test_printer_bidi); + torture_tcase_add_simple_test(tcase, "publish_toggle", + test_printer_publish_toggle); + torture_tcase_add_simple_test(tcase, "print_job_enum", test_print_job_enum); + torture_tcase_add_simple_test(tcase, "log_jobinfo", test_printer_log_jobinfo); + torture_tcase_add_simple_test(tcase, "os_versions", test_printer_os_versions); +} + +struct torture_suite *torture_rpc_spoolss_printer(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "printer"); + struct torture_tcase *tcase; + + tcase = torture_suite_add_tcase(suite, "addprinter"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_printer_setup, + torture_rpc_spoolss_printer_teardown); + + torture_tcase_printer(tcase); + + tcase = torture_suite_add_tcase(suite, "addprinterex"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_printerex_setup, + torture_rpc_spoolss_printer_teardown); + + torture_tcase_printer(tcase); + + tcase = torture_suite_add_tcase(suite, "addprinterwkn"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_printerwkn_setup, + torture_rpc_spoolss_printer_teardown); + + tcase = torture_suite_add_tcase(suite, "addprinterexwkn"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_printerexwkn_setup, + torture_rpc_spoolss_printer_teardown); + +#if 0 + /* test is not correct */ + tcase = torture_suite_add_tcase(suite, "addprinterdm"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_printerdm_setup, + torture_rpc_spoolss_printer_teardown); + + torture_tcase_printer(tcase); +#endif + return suite; +} + +struct torture_suite *torture_rpc_spoolss(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss"); + struct torture_tcase *tcase = torture_suite_add_tcase(suite, "printserver"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_setup, + torture_rpc_spoolss_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter_badnamelist", test_OpenPrinter_badname_list); + torture_tcase_add_simple_test(tcase, "printer_data_list", test_GetPrinterData_list); + torture_tcase_add_simple_test(tcase, "enum_forms", test_PrintServer_EnumForms); + torture_tcase_add_simple_test(tcase, "forms", test_PrintServer_Forms); + torture_tcase_add_simple_test(tcase, "forms_winreg", test_PrintServer_Forms_Winreg); + torture_tcase_add_simple_test(tcase, "enum_ports", test_EnumPorts); + torture_tcase_add_simple_test(tcase, "add_port", test_AddPort); + torture_tcase_add_simple_test(tcase, "get_printer_driver_directory", test_GetPrinterDriverDirectory); + torture_tcase_add_simple_test(tcase, "get_print_processor_directory", test_GetPrintProcessorDirectory); + torture_tcase_add_simple_test(tcase, "enum_printer_drivers", test_EnumPrinterDrivers); + torture_tcase_add_simple_test(tcase, "enum_monitors", test_EnumMonitors); + torture_tcase_add_simple_test(tcase, "enum_print_processors", test_EnumPrintProcessors); + torture_tcase_add_simple_test(tcase, "print_processors_winreg", test_print_processors_winreg); + torture_tcase_add_simple_test(tcase, "add_processor", test_add_print_processor); + torture_tcase_add_simple_test(tcase, "enum_printprocdata", test_EnumPrintProcessorDataTypes); + torture_tcase_add_simple_test(tcase, "enum_printers", test_EnumPrinters); + torture_tcase_add_simple_test(tcase, "enum_ports_old", test_EnumPorts_old); + torture_tcase_add_simple_test(tcase, "enum_printers_old", test_EnumPrinters_old); + torture_tcase_add_simple_test(tcase, "enum_printers_servername", test_EnumPrinters_servername); + torture_tcase_add_simple_test(tcase, "enum_printer_drivers_old", test_EnumPrinterDrivers_old); + torture_tcase_add_simple_test(tcase, "architecture_buffer", test_architecture_buffer); + torture_tcase_add_simple_test(tcase, "get_core_printer_drivers", test_get_core_printer_drivers); + torture_tcase_add_simple_test(tcase, "get_printer_driver_package_path", test_get_printer_driver_package_path); + torture_tcase_add_simple_test(tcase, "get_printer", test_get_printer_printserverhandle); + torture_tcase_add_simple_test(tcase, "set_printer", test_set_printer_printserverhandle); + torture_tcase_add_simple_test(tcase, "printserver_info_winreg", test_printserver_info_winreg); + torture_tcase_add_simple_test(tcase, "addpermachineconnection", test_addpermachineconnection); + + torture_suite_add_suite(suite, torture_rpc_spoolss_printer(suite)); + + return suite; +} + +static bool test_GetPrinterDriverDirectory_getdir(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server, + const char *environment, + const char **dir_p) +{ + struct spoolss_GetPrinterDriverDirectory r; + uint32_t needed; + + r.in.server = server; + r.in.environment = environment; + r.in.level = 1; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverDirectory_r(b, tctx, &r), + "failed to query driver directory"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_GetPrinterDriverDirectory_r(b, tctx, &r), + "failed to query driver directory"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "failed to query driver directory"); + + if (dir_p) { + *dir_p = r.out.info->info1.directory_name; + } + + return true; +} + +static const char *get_driver_from_info(struct spoolss_AddDriverInfoCtr *info_ctr) +{ + if (info_ctr == NULL) { + return NULL; + } + + switch (info_ctr->level) { + case 1: + return info_ctr->info.info1->driver_name; + case 2: + return info_ctr->info.info2->driver_name; + case 3: + return info_ctr->info.info3->driver_name; + case 4: + return info_ctr->info.info4->driver_name; + case 6: + return info_ctr->info.info6->driver_name; + case 8: + return info_ctr->info.info8->driver_name; + default: + return NULL; + } +} + +static const char *get_environment_from_info(struct spoolss_AddDriverInfoCtr *info_ctr) +{ + if (info_ctr == NULL) { + return NULL; + } + + switch (info_ctr->level) { + case 2: + return info_ctr->info.info2->architecture; + case 3: + return info_ctr->info.info3->architecture; + case 4: + return info_ctr->info.info4->architecture; + case 6: + return info_ctr->info.info6->architecture; + case 8: + return info_ctr->info.info8->architecture; + default: + return NULL; + } +} + + +static bool test_AddPrinterDriver_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, + struct spoolss_AddDriverInfoCtr *info_ctr, + WERROR expected_result) +{ + struct spoolss_AddPrinterDriver r; + const char *drivername = get_driver_from_info(info_ctr); + const char *environment = get_environment_from_info(info_ctr); + + r.in.servername = servername; + r.in.info_ctr = info_ctr; + + torture_comment(tctx, "Testing AddPrinterDriver(%s) level: %d, environment: '%s'\n", + drivername, info_ctr->level, environment); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_AddPrinterDriver_r(b, tctx, &r), + "spoolss_AddPrinterDriver failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "spoolss_AddPrinterDriver failed with unexpected result"); + + return true; + +} + +static bool test_AddPrinterDriverEx_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *servername, + struct spoolss_AddDriverInfoCtr *info_ctr, + uint32_t flags, + WERROR expected_result) +{ + struct spoolss_AddPrinterDriverEx r; + const char *drivername = get_driver_from_info(info_ctr); + const char *environment = get_environment_from_info(info_ctr); + + r.in.servername = servername; + r.in.info_ctr = info_ctr; + r.in.flags = flags; + + torture_comment(tctx, "Testing AddPrinterDriverEx(%s) level: %d, environment: '%s'\n", + drivername, info_ctr->level, environment); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_AddPrinterDriverEx_r(b, tctx, &r), + "AddPrinterDriverEx failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "AddPrinterDriverEx failed with unexpected result"); + + return true; +} + +#define ASSERT_DRIVER_PATH(tctx, path, driver_dir, cmt) \ + if (path && strlen(path)) {\ + torture_assert_strn_equal(tctx, path, driver_dir, strlen(driver_dir), cmt); \ + } + +static bool test_AddPrinterDriver_args_level_1(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + struct spoolss_AddDriverInfo1 info1; + + ZERO_STRUCT(info1); + + info_ctr.level = 1; + info_ctr.info.info1 = &info1; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriverEx level 1"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriver level 1"); + } + + info1.driver_name = r->driver_name; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriverEx level 1"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriver level 1"); + } + + return true; +} + +static bool test_AddPrinterDriver_args_level_2(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + struct spoolss_AddDriverInfo2 info2; + union spoolss_DriverInfo info; + + ZERO_STRUCT(info2); + + info_ctr.level = 2; + info_ctr.info.info2 = &info2; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.driver_name = r->driver_name; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.version = r->version; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.architecture = r->architecture; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.driver_path = r->driver_path; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.data_file = r->data_file; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriver level 2"); + } + + info2.config_file = r->config_file; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, 0, WERR_INVALID_PARAMETER), + "failed to test AddPrinterDriverEx"); + } + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_OK), + "failed to test AddPrinterDriverEx level 2"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_OK), + "failed to test AddPrinterDriver level 2"); + } + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name, r->architecture, 2, r->driver_name, &info), + "failed to find added printer driver"); + + if (remote_driver_dir) { + ASSERT_DRIVER_PATH(tctx, info.info2.driver_path, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info2.data_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info2.config_file, remote_driver_dir, "unexpected path"); + } + + return true; +} + +static bool test_AddPrinterDriver_args_level_3(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + struct spoolss_AddDriverInfo3 info3; + union spoolss_DriverInfo info; + + info3.driver_name = r->driver_name; + info3.version = r->version; + info3.architecture = r->architecture; + info3.driver_path = r->driver_path; + info3.data_file = r->data_file; + info3.config_file = r->config_file; + info3.help_file = r->help_file; + info3.monitor_name = r->monitor_name; + info3.default_datatype = r->default_datatype; + info3._ndr_size_dependent_files = r->_ndr_size_dependent_files; + info3.dependent_files = r->dependent_files; + + info_ctr.level = 3; + info_ctr.info.info3 = &info3; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_OK), + "failed to test AddPrinterDriverEx level 3"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_OK), + "failed to test AddPrinterDriver level 3"); + } + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name, r->architecture, 3, r->driver_name, &info), + "failed to find added printer driver"); + + if (remote_driver_dir) { + int i; + ASSERT_DRIVER_PATH(tctx, info.info3.driver_path, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info3.data_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info3.config_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info3.help_file, remote_driver_dir, "unexpected path"); + for (i=0; info.info3.dependent_files && info.info3.dependent_files[i] != NULL; i++) { + ASSERT_DRIVER_PATH(tctx, info.info3.dependent_files[i], remote_driver_dir, "unexpected path"); + } + } + + return true; +} + +static bool test_AddPrinterDriver_args_level_4(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + struct spoolss_AddDriverInfo4 info4; + union spoolss_DriverInfo info; + + info4.version = r->version; + info4.driver_name = r->driver_name; + info4.architecture = r->architecture; + info4.driver_path = r->driver_path; + info4.data_file = r->data_file; + info4.config_file = r->config_file; + info4.help_file = r->help_file; + info4.monitor_name = r->monitor_name; + info4.default_datatype = r->default_datatype; + info4._ndr_size_dependent_files = r->_ndr_size_dependent_files; + info4.dependent_files = r->dependent_files; + info4._ndr_size_previous_names = r->_ndr_size_previous_names; + info4.previous_names = r->previous_names; + + info_ctr.level = 4; + info_ctr.info.info4 = &info4; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_OK), + "failed to test AddPrinterDriverEx level 4"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_OK), + "failed to test AddPrinterDriver level 4"); + } + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name, r->architecture, 4, r->driver_name, &info), + "failed to find added printer driver"); + + if (remote_driver_dir) { + int i; + ASSERT_DRIVER_PATH(tctx, info.info4.driver_path, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info4.data_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info4.config_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info4.help_file, remote_driver_dir, "unexpected path"); + for (i=0; info.info4.dependent_files && info.info4.dependent_files[i] != NULL; i++) { + ASSERT_DRIVER_PATH(tctx, info.info4.dependent_files[i], remote_driver_dir, "unexpected path"); + } + } + + return true; +} + +static bool test_AddPrinterDriver_args_level_6(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + struct spoolss_AddDriverInfo6 info6; + union spoolss_DriverInfo info; + + info6.version = r->version; + info6.driver_name = r->driver_name; + info6.architecture = r->architecture; + info6.driver_path = r->driver_path; + info6.data_file = r->data_file; + info6.config_file = r->config_file; + info6.help_file = r->help_file; + info6.monitor_name = r->monitor_name; + info6.default_datatype = r->default_datatype; + info6._ndr_size_dependent_files = r->_ndr_size_dependent_files; + info6.dependent_files = r->dependent_files; + info6._ndr_size_previous_names = r->_ndr_size_previous_names; + info6.previous_names = r->previous_names; + info6.driver_date = r->driver_date; + info6.driver_version = r->driver_version; + info6.manufacturer_name = r->manufacturer_name; + info6.manufacturer_url = r->manufacturer_url; + info6.hardware_id = r->hardware_id; + info6.provider = r->provider; + + info_ctr.level = 6; + info_ctr.info.info6 = &info6; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_OK), + "failed to test AddPrinterDriverEx level 6"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriver level 6"); + } + + /* spoolss_AddPrinterDriver does not deal with level 6 or 8 - gd */ + + if (!ex) { + return true; + } + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name, r->architecture, 6, r->driver_name, &info), + "failed to find added printer driver"); + + if (remote_driver_dir) { + int i; + ASSERT_DRIVER_PATH(tctx, info.info6.driver_path, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info6.data_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info6.config_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info6.help_file, remote_driver_dir, "unexpected path"); + for (i=0; info.info6.dependent_files && info.info6.dependent_files[i] != NULL; i++) { + ASSERT_DRIVER_PATH(tctx, info.info6.dependent_files[i], remote_driver_dir, "unexpected path"); + } + } + + torture_assert_nttime_equal(tctx, info.info6.driver_date, info6.driver_date, "driverdate mismatch"); + torture_assert_u64_equal(tctx, info.info6.driver_version, info6.driver_version, "driverversion mismatch"); + + return true; +} + +static bool test_AddPrinterDriver_args_level_8(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + struct spoolss_AddDriverInfo8 *r, + uint32_t flags, + bool ex, + const char *remote_driver_dir) +{ + struct spoolss_AddDriverInfoCtr info_ctr; + union spoolss_DriverInfo info; + + info_ctr.level = 8; + info_ctr.info.info8 = r; + + if (ex) { + torture_assert(tctx, + test_AddPrinterDriverEx_exp(tctx, b, server_name, &info_ctr, flags, WERR_OK), + "failed to test AddPrinterDriverEx level 8"); + } else { + torture_assert(tctx, + test_AddPrinterDriver_exp(tctx, b, server_name, &info_ctr, WERR_INVALID_LEVEL), + "failed to test AddPrinterDriver level 8"); + } + + /* spoolss_AddPrinterDriver does not deal with level 6 or 8 - gd */ + + if (!ex) { + return true; + } + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name, r->architecture, 8, r->driver_name, &info), + "failed to find added printer driver"); + + if (remote_driver_dir) { + int i; + ASSERT_DRIVER_PATH(tctx, info.info8.driver_path, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info8.data_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info8.config_file, remote_driver_dir, "unexpected path"); + ASSERT_DRIVER_PATH(tctx, info.info8.help_file, remote_driver_dir, "unexpected path"); + for (i=0; info.info8.dependent_files && info.info8.dependent_files[i] != NULL; i++) { + ASSERT_DRIVER_PATH(tctx, info.info8.dependent_files[i], remote_driver_dir, "unexpected path"); + } + } + + torture_assert_nttime_equal(tctx, info.info8.driver_date, r->driver_date, "driverdate mismatch"); + torture_assert_u64_equal(tctx, info.info8.driver_version, r->driver_version, "driverversion mismatch"); + + return true; +} + +#undef ASSERT_DRIVER_PATH + +static bool test_DeletePrinterDriver_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server, + const char *driver, + const char *environment, + WERROR expected_result) +{ + struct spoolss_DeletePrinterDriver r; + + r.in.server = server; + r.in.architecture = environment; + r.in.driver = driver; + + torture_comment(tctx, "Testing DeletePrinterDriver(%s)\n", driver); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrinterDriver_r(b, tctx, &r), + "DeletePrinterDriver failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "DeletePrinterDriver failed with unexpected result"); + + return true; +} + +static bool test_DeletePrinterDriverEx_exp(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server, + const char *driver, + const char *environment, + uint32_t delete_flags, + uint32_t version, + WERROR expected_result) +{ + struct spoolss_DeletePrinterDriverEx r; + + r.in.server = server; + r.in.architecture = environment; + r.in.driver = driver; + r.in.delete_flags = delete_flags; + r.in.version = version; + + torture_comment(tctx, "Testing DeletePrinterDriverEx(%s)\n", driver); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_DeletePrinterDriverEx_r(b, tctx, &r), + "DeletePrinterDriverEx failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "DeletePrinterDriverEx failed with unexpected result"); + + return true; +} + +static bool test_DeletePrinterDriver(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + const char *driver, + const char *environment) +{ + torture_assert(tctx, + test_DeletePrinterDriver_exp(tctx, b, server_name, driver, "FOOBAR", WERR_INVALID_ENVIRONMENT), + "failed to delete driver"); + + torture_assert(tctx, + test_DeletePrinterDriver_exp(tctx, b, server_name, driver, environment, WERR_OK), + "failed to delete driver"); + + if (test_EnumPrinterDrivers_findone(tctx, b, server_name, environment, 1, driver, NULL)) { + torture_fail(tctx, "deleted driver still enumerated"); + } + + torture_assert(tctx, + test_DeletePrinterDriver_exp(tctx, b, server_name, driver, environment, WERR_UNKNOWN_PRINTER_DRIVER), + "2nd delete failed"); + + return true; +} + +static bool test_DeletePrinterDriverEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + const char *driver, + const char *environment, + uint32_t delete_flags, + uint32_t version) +{ + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name, driver, "FOOBAR", delete_flags, version, WERR_INVALID_ENVIRONMENT), + "failed to delete driver"); + + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name, driver, environment, delete_flags, version, WERR_OK), + "failed to delete driver"); + + if (test_EnumPrinterDrivers_findone(tctx, b, server_name, environment, 1, driver, NULL)) { + torture_fail(tctx, "deleted driver still enumerated"); + } + + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name, driver, environment, delete_flags, version, WERR_UNKNOWN_PRINTER_DRIVER), + "2nd delete failed"); + + return true; +} + +static bool test_PrinterDriver_args(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *server_name, + uint32_t level, + struct spoolss_AddDriverInfo8 *r, + uint32_t add_flags, + uint32_t delete_flags, + uint32_t delete_version, + bool ex, + const char *remote_driver_dir) +{ + bool ret = true; + + switch (level) { + case 1: + ret = test_AddPrinterDriver_args_level_1(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + case 2: + ret = test_AddPrinterDriver_args_level_2(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + case 3: + ret = test_AddPrinterDriver_args_level_3(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + case 4: + ret = test_AddPrinterDriver_args_level_4(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + case 6: + ret = test_AddPrinterDriver_args_level_6(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + case 8: + ret = test_AddPrinterDriver_args_level_8(tctx, b, server_name, r, add_flags, ex, remote_driver_dir); + break; + default: + return false; + } + + if (ret == false) { + return ret; + } + + if (level == 1) { + return ret; + } + + /* spoolss_AddPrinterDriver does not deal with level 6 or 8 - gd */ + + if (!ex && (level == 6 || level == 8)) { + return ret; + } + + { + struct dcerpc_pipe *p2; + struct policy_handle hive_handle; + struct dcerpc_binding_handle *b2; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &p2, &ndr_table_winreg), + "could not open winreg pipe"); + b2 = p2->binding_handle; + + torture_assert(tctx, test_winreg_OpenHKLM(tctx, b2, &hive_handle), ""); + + ret = test_GetDriverInfo_winreg(tctx, b, NULL, NULL, r->driver_name, r->architecture, r->version, b2, &hive_handle, server_name); + + test_winreg_CloseKey(tctx, b2, &hive_handle); + + talloc_free(p2); + } + + if (ex) { + return test_DeletePrinterDriverEx(tctx, b, server_name, r->driver_name, r->architecture, delete_flags, r->version); + } else { + return test_DeletePrinterDriver(tctx, b, server_name, r->driver_name, r->architecture); + } +} + +static bool fillup_printserver_info(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct torture_driver_context *d) +{ + struct policy_handle server_handle; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + torture_assert(tctx, + test_OpenPrinter_server(tctx, p, &server_handle), + "failed to open printserver"); + torture_assert(tctx, + test_get_environment(tctx, b, &server_handle, &d->remote.environment), + "failed to get environment"); + torture_assert(tctx, + test_ClosePrinter(tctx, b, &server_handle), + "failed to close printserver"); + + torture_assert(tctx, + test_GetPrinterDriverDirectory_getdir(tctx, b, server_name_slash, + d->local.environment ? d->local.environment : d->remote.environment, + &d->remote.driver_directory), + "failed to get driver directory"); + + return true; +} + +static const char *driver_directory_dir(const char *driver_directory) +{ + char *p; + + p = strrchr(driver_directory, '\\'); + if (p) { + return p+1; + } + + return NULL; +} + +static const char *driver_directory_share(struct torture_context *tctx, + const char *driver_directory) +{ + const char *p; + char *tok; + + if (driver_directory[0] == '\\' && driver_directory[1] == '\\') { + driver_directory += 2; + } + + p = talloc_strdup(tctx, driver_directory); + + torture_assert(tctx, + next_token_talloc(tctx, &p, &tok, "\\"), + "cannot explode uri"); + torture_assert(tctx, + next_token_talloc(tctx, &p, &tok, "\\"), + "cannot explode uri"); + + return tok; +} + +#define CREATE_PRINTER_DRIVER_PATH(_d, _file) \ + talloc_asprintf((_d), "%s\\%s\\%s", (_d)->remote.driver_directory, (_d)->remote.driver_upload_directory, (_file)) + + +static bool create_printer_driver_directory(struct torture_context *tctx, + struct smbcli_state *cli, + struct torture_driver_context *d) +{ + char *driver_dir; + + if (d->remote.driver_upload_directory == NULL) { + return true; + } + + driver_dir = talloc_asprintf(tctx, + "%s\\%s", + driver_directory_dir(d->remote.driver_directory), + d->remote.driver_upload_directory); + torture_assert_not_null(tctx, driver_dir, "ENOMEM"); + + torture_comment(tctx, + "Create remote driver directory: %s\n", + driver_dir); + + torture_assert_ntstatus_ok(tctx, + smbcli_mkdir(cli->tree, + driver_dir), + "Failed to create driver directory"); + + return true; +} + +static bool upload_printer_driver_file(struct torture_context *tctx, + struct smbcli_state *cli, + struct torture_driver_context *d, + const char *file_name) +{ + FILE *f; + int fnum; + uint8_t *buf; + int maxwrite = 64512; + off_t nread = 0; + size_t start = 0; + const char *remote_dir = driver_directory_dir(d->remote.driver_directory); + const char *remote_name; + const char *local_name; + const char *p; + + if (!file_name || strlen(file_name) == 0) { + return true; + } + + p = strrchr(file_name, '\\'); + if (p == NULL) { + p = file_name; + } else { + p++; + } + + local_name = talloc_asprintf(tctx, "%s/%s", d->local.driver_directory, p); + torture_assert_not_null(tctx, local_name, "ENOMEM"); + if (d->remote.driver_upload_directory != NULL) { + remote_name = talloc_asprintf(tctx, + "%s\\%s\\%s", + remote_dir, + d->remote.driver_upload_directory, + p); + } else { + remote_name = talloc_asprintf(tctx, "%s\\%s", remote_dir, p); + } + torture_assert_not_null(tctx, remote_name, "ENOMEM"); + + torture_comment(tctx, "Uploading %s to %s\n", local_name, remote_name); + + fnum = smbcli_open(cli->tree, remote_name, O_RDWR|O_CREAT|O_TRUNC, DENY_NONE); + if (fnum == -1) { + torture_fail(tctx, talloc_asprintf(tctx, "failed to open remote file: %s\n", remote_name)); + } + + f = fopen(local_name, "r"); + if (f == NULL) { + torture_fail(tctx, talloc_asprintf(tctx, "failed to open local file: %s\n", local_name)); + } + + buf = talloc_array(tctx, uint8_t, maxwrite); + if (!buf) { + fclose(f); + return false; + } + + while (!feof(f)) { + int n = maxwrite; + int ret; + + if ((n = fread(buf, 1, n, f)) < 1) { + if((n == 0) && feof(f)) + break; /* Empty local file. */ + + torture_warning(tctx, + "failed to read file: %s\n", strerror(errno)); + break; + } + + ret = smbcli_write(cli->tree, fnum, 0, buf, nread + start, n); + + if (n != ret) { + torture_warning(tctx, + "failed to write file: %s\n", smbcli_errstr(cli->tree)); + break; + } + + nread += n; + } + + fclose(f); + + torture_assert_ntstatus_ok(tctx, + smbcli_close(cli->tree, fnum), + "failed to close file"); + + return true; +} + +static bool connect_printer_driver_share(struct torture_context *tctx, + const char *server_name, + const char *share_name, + struct smbcli_state **cli) +{ + struct smbcli_options smb_options; + struct smbcli_session_options smb_session_options; + + torture_comment(tctx, "Connecting printer driver share '%s' on '%s'\n", + share_name, server_name); + + lpcfg_smbcli_options(tctx->lp_ctx, &smb_options); + lpcfg_smbcli_session_options(tctx->lp_ctx, &smb_session_options); + + torture_assert_ntstatus_ok(tctx, + smbcli_full_connection(tctx, cli, server_name, + lpcfg_smb_ports(tctx->lp_ctx), + share_name, NULL, + lpcfg_socket_options(tctx->lp_ctx), + samba_cmdline_get_creds(), + lpcfg_resolve_context(tctx->lp_ctx), + tctx->ev, + &smb_options, + &smb_session_options, + lpcfg_gensec_settings(tctx, tctx->lp_ctx)), + "failed to open driver share"); + + return true; +} + +static bool upload_printer_driver(struct torture_context *tctx, + const char *server_name, + struct torture_driver_context *d) +{ + struct smbcli_state *cli; + const char *share_name = driver_directory_share(tctx, d->remote.driver_directory); + int i; + + torture_assert(tctx, + connect_printer_driver_share(tctx, server_name, share_name, &cli), + "failed to connect to driver share"); + + torture_comment(tctx, "Uploading printer driver files to \\\\%s\\%s\n", + server_name, share_name); + + torture_assert(tctx, + create_printer_driver_directory(tctx, cli, d), + "failed to create driver directory"); + + torture_assert(tctx, + upload_printer_driver_file(tctx, cli, d, d->info8.driver_path), + "failed to upload driver_path"); + torture_assert(tctx, + upload_printer_driver_file(tctx, cli, d, d->info8.data_file), + "failed to upload data_file"); + torture_assert(tctx, + upload_printer_driver_file(tctx, cli, d, d->info8.config_file), + "failed to upload config_file"); + torture_assert(tctx, + upload_printer_driver_file(tctx, cli, d, d->info8.help_file), + "failed to upload help_file"); + if (d->info8.dependent_files) { + for (i=0; d->info8.dependent_files->string && d->info8.dependent_files->string[i] != NULL; i++) { + torture_assert(tctx, + upload_printer_driver_file(tctx, cli, d, d->info8.dependent_files->string[i]), + "failed to upload dependent_files"); + } + } + + talloc_free(cli); + + return true; +} + +static bool check_printer_driver_file(struct torture_context *tctx, + struct smbcli_state *cli, + struct torture_driver_context *d, + const char *file_name) +{ + const char *remote_arch_dir = driver_directory_dir(d->remote.driver_directory); + const char *remote_name = talloc_asprintf(tctx, "%s\\%d\\%s", + remote_arch_dir, + d->info8.version, + file_name); + int fnum; + + torture_assert(tctx, (file_name && strlen(file_name) != 0), "invalid filename"); + + torture_comment(tctx, "checking for driver file at %s\n", remote_name); + + fnum = smbcli_open(cli->tree, remote_name, O_RDONLY, DENY_NONE); + if (fnum == -1) { + return false; + } + + torture_assert_ntstatus_ok(tctx, + smbcli_close(cli->tree, fnum), + "failed to close driver file"); + + return true; +} + +static bool check_printer_driver_files(struct torture_context *tctx, + const char *server_name, + struct torture_driver_context *d, + bool expect_exist) +{ + struct smbcli_state *cli; + const char *share_name = driver_directory_share(tctx, d->remote.driver_directory); + int i; + + torture_assert(tctx, + connect_printer_driver_share(tctx, server_name, share_name, &cli), + "failed to connect to driver share"); + + torture_comment(tctx, "checking %sexistent driver files at \\\\%s\\%s\n", + (expect_exist ? "": "non-"), + server_name, share_name); + + if (d->info8.driver_path && d->info8.driver_path[0]) { + torture_assert(tctx, + check_printer_driver_file(tctx, cli, d, d->info8.driver_path) == expect_exist, + "failed driver_path check"); + } + if (d->info8.data_file && d->info8.data_file[0]) { + torture_assert(tctx, + check_printer_driver_file(tctx, cli, d, d->info8.data_file) == expect_exist, + "failed data_file check"); + } + if (d->info8.config_file && d->info8.config_file[0]) { + torture_assert(tctx, + check_printer_driver_file(tctx, cli, d, d->info8.config_file) == expect_exist, + "failed config_file check"); + } + if (d->info8.help_file && d->info8.help_file[0]) { + torture_assert(tctx, + check_printer_driver_file(tctx, cli, d, d->info8.help_file) == expect_exist, + "failed help_file check"); + } + if (d->info8.dependent_files) { + for (i=0; d->info8.dependent_files->string && d->info8.dependent_files->string[i] != NULL; i++) { + torture_assert(tctx, + check_printer_driver_file(tctx, cli, d, d->info8.dependent_files->string[i]) == expect_exist, + "failed dependent_files check"); + } + } + + talloc_free(cli); + + return true; +} + +static bool remove_printer_driver_file(struct torture_context *tctx, + struct smbcli_state *cli, + struct torture_driver_context *d, + const char *file_name) +{ + const char *remote_name; + const char *remote_dir = driver_directory_dir(d->remote.driver_directory); + + if (!file_name || strlen(file_name) == 0) { + return true; + } + + remote_name = talloc_asprintf(tctx, "%s\\%s", remote_dir, file_name); + + torture_comment(tctx, "Removing %s\n", remote_name); + + torture_assert_ntstatus_ok(tctx, + smbcli_unlink(cli->tree, remote_name), + "failed to unlink"); + + return true; +} + +static bool remove_printer_driver(struct torture_context *tctx, + const char *server_name, + struct torture_driver_context *d) +{ + struct smbcli_state *cli; + const char *share_name = driver_directory_share(tctx, d->remote.driver_directory); + int i; + + torture_assert(tctx, + connect_printer_driver_share(tctx, server_name, share_name, &cli), + "failed to connect to driver share"); + + torture_comment(tctx, "Removing printer driver files from \\\\%s\\%s\n", + server_name, share_name); + + torture_assert(tctx, + remove_printer_driver_file(tctx, cli, d, d->info8.driver_path), + "failed to remove driver_path"); + torture_assert(tctx, + remove_printer_driver_file(tctx, cli, d, d->info8.data_file), + "failed to remove data_file"); + if (!strequal(d->info8.config_file, d->info8.driver_path)) { + torture_assert(tctx, + remove_printer_driver_file(tctx, cli, d, d->info8.config_file), + "failed to remove config_file"); + } + torture_assert(tctx, + remove_printer_driver_file(tctx, cli, d, d->info8.help_file), + "failed to remove help_file"); + if (d->info8.dependent_files) { + for (i=0; d->info8.dependent_files->string && d->info8.dependent_files->string[i] != NULL; i++) { + if (strequal(d->info8.dependent_files->string[i], d->info8.driver_path) || + strequal(d->info8.dependent_files->string[i], d->info8.data_file) || + strequal(d->info8.dependent_files->string[i], d->info8.config_file) || + strequal(d->info8.dependent_files->string[i], d->info8.help_file)) { + continue; + } + torture_assert(tctx, + remove_printer_driver_file(tctx, cli, d, d->info8.dependent_files->string[i]), + "failed to remove dependent_files"); + } + } + + talloc_free(cli); + + return true; + +} + +static bool test_add_driver_arg(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct torture_driver_context *d) +{ + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + uint32_t levels[] = { 1, 2, 3, 4, 6, 8 }; + int i; + struct spoolss_AddDriverInfo8 info8; + uint32_t add_flags = APD_COPY_NEW_FILES; + uint32_t delete_flags = 0; + + ZERO_STRUCT(info8); + + torture_comment(tctx, "Testing PrinterDriver%s '%s' for environment '%s'\n", + d->ex ? "Ex" : "", d->info8.driver_name, d->local.environment); + + torture_assert(tctx, + fillup_printserver_info(tctx, p, d), + "failed to fillup printserver info"); + + if (!directory_exist(d->local.driver_directory)) { + torture_skip(tctx, "Skipping Printer Driver test as no local driver is available"); + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to upload printer driver"); + + info8 = d->info8; + if (d->info8.dependent_files) { + info8.dependent_files = talloc_zero(tctx, struct spoolss_StringArray); + if (d->info8.dependent_files->string) { + for (i=0; d->info8.dependent_files->string[i] != NULL; i++) { + } + info8.dependent_files->string = talloc_zero_array(info8.dependent_files, const char *, i+1); + for (i=0; d->info8.dependent_files->string[i] != NULL; i++) { + info8.dependent_files->string[i] = talloc_strdup(info8.dependent_files->string, d->info8.dependent_files->string[i]); + } + } + } + + for (i=0; i < ARRAY_SIZE(levels); i++) { + + if (torture_setting_bool(tctx, "samba3", false)) { + switch (levels[i]) { + case 2: + case 4: + torture_comment(tctx, "skipping level %d against samba\n", levels[i]); + continue; + default: + break; + } + } + if (torture_setting_bool(tctx, "w2k3", false)) { + switch (levels[i]) { + case 8: + torture_comment(tctx, "skipping level %d against w2k3\n", levels[i]); + continue; + default: + break; + } + } + + torture_comment(tctx, + "Testing PrinterDriver%s '%s' add & delete level %d\n", + d->ex ? "Ex" : "", info8.driver_name, levels[i]); + + ret &= test_PrinterDriver_args(tctx, b, server_name_slash, levels[i], &info8, add_flags, delete_flags, d->info8.version, d->ex, d->remote.driver_directory); + } + + info8.driver_path = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.driver_path); + info8.data_file = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.data_file); + if (d->info8.config_file) { + info8.config_file = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.config_file); + } + if (d->info8.help_file) { + info8.help_file = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.help_file); + } + if (d->info8.dependent_files && d->info8.dependent_files->string) { + for (i=0; d->info8.dependent_files->string[i] != NULL; i++) { + info8.dependent_files->string[i] = talloc_asprintf(tctx, "%s\\%s", d->remote.driver_directory, d->info8.dependent_files->string[i]); + } + } + + for (i=0; i < ARRAY_SIZE(levels); i++) { + + if (torture_setting_bool(tctx, "samba3", false)) { + switch (levels[i]) { + case 2: + case 4: + continue; + default: + break; + } + } + if (torture_setting_bool(tctx, "w2k3", false)) { + switch (levels[i]) { + case 8: + torture_comment(tctx, "skipping level %d against w2k3\n", levels[i]); + continue; + default: + break; + } + } + + torture_comment(tctx, + "Testing PrinterDriver%s '%s' add & delete level %d (full unc paths)\n", + d->ex ? "Ex" : "", info8.driver_name, levels[i]); + + ret &= test_PrinterDriver_args(tctx, b, server_name_slash, levels[i], &info8, add_flags, delete_flags, d->info8.version, d->ex, d->remote.driver_directory); + } + + torture_assert(tctx, + remove_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to remove printer driver"); + + torture_comment(tctx, "\n"); + + return ret; +} + +static bool test_add_driver_ex_64(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_x64); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/x64"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_EX; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->ex = true; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_ex_32(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_NT_X86); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/i386"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_EX; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->ex = true; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_64(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_x64); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/x64"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_ADD; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->ex = false; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_32(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_NT_X86); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/i386"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_ADD; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->ex = false; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_adobe(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + + if (!torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping adobe test which only works against samba3"); + } + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, "Windows 4.0"); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/adobe/"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_9X; + d->info8.driver_name = TORTURE_DRIVER_ADOBE; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "ADOBEPS4.DRV"); + d->info8.data_file = talloc_strdup(d, "DEFPRTR2.PPD"); + d->info8.config_file = talloc_strdup(d, "ADOBEPS4.DRV"); +#if 0 + d->info8.help_file = talloc_strdup(d, "ADOBEPS4.HLP"); + d->info8.monitor_name = talloc_strdup(d, "PSMON.DLL"); +#endif + d->ex = false; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_adobe_cupsaddsmb(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + struct spoolss_StringArray *a; + + if (!torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping cupsaddsmb test which only works against samba3"); + } + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, "Windows 4.0"); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/adobe/"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_9X; + d->info8.driver_name = TORTURE_DRIVER_ADOBE_CUPSADDSMB; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "ADOBEPS4.DRV"); + d->info8.data_file = talloc_strdup(d, "DEFPRTR2.PPD"); + d->info8.config_file = NULL; + d->info8.help_file = talloc_strdup(d, "ADOBEPS4.HLP"); + d->info8.monitor_name = talloc_strdup(d, "PSMON.DLL"); + d->info8.default_datatype = talloc_strdup(d, "RAW"); + + a = talloc_zero(d, struct spoolss_StringArray); + a->string = talloc_zero_array(a, const char *, 7); + a->string[0] = talloc_strdup(a->string, "ADOBEPS4.DRV"); + a->string[1] = talloc_strdup(a->string, "DEFPRTR2.PPD"); + a->string[2] = talloc_strdup(a->string, "ADOBEPS4.HLP"); + a->string[3] = talloc_strdup(a->string, "PSMON.DLL"); + a->string[4] = talloc_strdup(a->string, "ADFONTS.MFM"); + a->string[5] = talloc_strdup(a->string, "ICONLIB.DLL"); + + d->info8.dependent_files = a; + d->ex = false; + + return test_add_driver_arg(tctx, p, d); +} + +static bool test_add_driver_timestamps(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + struct timeval t = timeval_current(); + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_NT_X86); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/i386"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_TIMESTAMPS; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->info8.driver_date = timeval_to_nttime(&t); + d->ex = true; + + torture_assert(tctx, + test_add_driver_arg(tctx, p, d), + ""); + + unix_to_nt_time(&d->info8.driver_date, 1); + + torture_assert(tctx, + test_add_driver_arg(tctx, p, d), + ""); + + return true; +} + +static bool test_multiple_drivers(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + int i; + struct spoolss_AddDriverInfo8 info8; + uint32_t add_flags = APD_COPY_NEW_FILES; + uint32_t delete_flags = 0; + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_NT_X86); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/i386"); + + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->info8.architecture = d->local.environment; + d->ex = true; + + torture_assert(tctx, + fillup_printserver_info(tctx, p, d), + "failed to fillup printserver info"); + + if (!directory_exist(d->local.driver_directory)) { + torture_skip(tctx, "Skipping Printer Driver test as no local driver is available"); + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to upload printer driver"); + + info8 = d->info8; + + for (i=0; i < 3; i++) { + info8.driver_name = talloc_asprintf(d, "torture_test_driver_%d", i); + + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &info8, add_flags, true, NULL), + "failed to add driver"); + } + + torture_assert(tctx, + test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_0", info8.architecture, delete_flags, info8.version), + "failed to delete driver"); + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_1", NULL), + "torture_test_driver_1 no longer on the server"); + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_2", NULL), + "torture_test_driver_2 no longer on the server"); + + torture_assert(tctx, + test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_1", info8.architecture, delete_flags, info8.version), + "failed to delete driver"); + + torture_assert(tctx, + test_EnumPrinterDrivers_findone(tctx, b, server_name_slash, info8.architecture, 3, "torture_test_driver_2", NULL), + "torture_test_driver_2 no longer on the server"); + + torture_assert(tctx, + test_DeletePrinterDriverEx(tctx, b, server_name_slash, "torture_test_driver_2", info8.architecture, delete_flags, info8.version), + "failed to delete driver"); + + torture_assert(tctx, + remove_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to remove printer driver"); + + return true; +} + +static bool test_driver_copy_from_directory(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *architecture) +{ + struct torture_driver_context *d; + struct spoolss_StringArray *a; + uint32_t add_flags = APD_COPY_NEW_FILES|APD_COPY_FROM_DIRECTORY|APD_RETURN_BLOCKING_STATUS_CODE; + uint32_t delete_flags = DPD_DELETE_ALL_FILES; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, + "\\\\%s", + dcerpc_server_name(p)); + struct GUID guid = GUID_random(); + bool ok = false; + + d = talloc_zero(tctx, struct torture_driver_context); + torture_assert_not_null(tctx, d, "ENOMEM"); + + d->local.environment = talloc_strdup(d, architecture); + torture_assert_not_null_goto(tctx, d->local.environment, ok, done, "ENOMEM"); + + if (strequal(architecture, SPOOLSS_ARCHITECTURE_x64)) { + d->local.driver_directory = + talloc_strdup(d, "/usr/share/cups/drivers/x64"); + } else { + d->local.driver_directory = + talloc_strdup(d, "/usr/share/cups/drivers/i386"); + } + torture_assert_not_null_goto(tctx, d->local.driver_directory, ok, done, "ENOMEM"); + + d->remote.driver_upload_directory = GUID_string2(d, &guid); + torture_assert_not_null_goto(tctx, d->remote.driver_upload_directory, ok, done, "ENOMEM"); + + torture_assert(tctx, + fillup_printserver_info(tctx, p, d), + "failed to fillup printserver info"); + + d->ex = true; + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_COPY_DIR; + d->info8.architecture = d->local.environment; + + d->info8.driver_path = CREATE_PRINTER_DRIVER_PATH(d, "pscript5.dll"); + torture_assert_not_null_goto(tctx, d->info8.driver_path, ok, done, "ENOMEM"); + d->info8.data_file = CREATE_PRINTER_DRIVER_PATH(d, "cups6.ppd"); + torture_assert_not_null_goto(tctx, d->info8.data_file, ok, done, "ENOMEM"); + d->info8.config_file = CREATE_PRINTER_DRIVER_PATH(d, "cupsui6.dll"); + torture_assert_not_null_goto(tctx, d->info8.config_file, ok, done, "ENOMEM"); + d->info8.help_file = CREATE_PRINTER_DRIVER_PATH(d, "pscript.hlp"); + torture_assert_not_null_goto(tctx, d->info8.help_file, ok, done, "ENOMEM"); + + a = talloc_zero(d, struct spoolss_StringArray); + torture_assert_not_null_goto(tctx, a, ok, done, "ENOMEM"); + a->string = talloc_zero_array(a, const char *, 3); + torture_assert_not_null_goto(tctx, a->string, ok, done, "ENOMEM"); + a->string[0] = CREATE_PRINTER_DRIVER_PATH(d, "cups6.inf"); + torture_assert_not_null_goto(tctx, a->string[0], ok, done, "ENOMEM"); + a->string[1] = CREATE_PRINTER_DRIVER_PATH(d, "cups6.ini"); + torture_assert_not_null_goto(tctx, a->string[1], ok, done, "ENOMEM"); + + d->info8.dependent_files = a; + + if (!directory_exist(d->local.driver_directory)) { + torture_skip(tctx, + "Skipping Printer Driver test as no local drivers " + "are available"); + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to upload printer driver"); + + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, + b, + server_name_slash, + &d->info8, + add_flags, + true, + NULL), + "failed to add driver"); + + torture_assert(tctx, + test_DeletePrinterDriverEx(tctx, + b, + server_name_slash, + d->info8.driver_name, + d->local.environment, + delete_flags, + d->info8.version), + "failed to delete driver"); + + torture_assert(tctx, + check_printer_driver_files(tctx, + dcerpc_server_name(p), + d, + false), + "printer driver file check failed"); + + ok = true; +done: + talloc_free(d); + return ok; +} + +static bool test_driver_copy_from_directory_64(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_driver_copy_from_directory(tctx, p, SPOOLSS_ARCHITECTURE_x64); +} + +static bool test_driver_copy_from_directory_32(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_driver_copy_from_directory(tctx, p, SPOOLSS_ARCHITECTURE_NT_X86); +} + +static bool test_del_driver_all_files(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d; + struct spoolss_StringArray *a; + uint32_t add_flags = APD_COPY_NEW_FILES; + uint32_t delete_flags = DPD_DELETE_ALL_FILES; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + d = talloc_zero(tctx, struct torture_driver_context); + + d->local.environment = talloc_strdup(d, SPOOLSS_ARCHITECTURE_x64); + d->local.driver_directory = talloc_strdup(d, "/usr/share/cups/drivers/x64"); + + d->ex = true; + d->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d->info8.driver_name = TORTURE_DRIVER_DELETER; + d->info8.architecture = d->local.environment; + d->info8.driver_path = talloc_strdup(d, "pscript5.dll"); + d->info8.data_file = talloc_strdup(d, "cups6.ppd"); + d->info8.config_file = talloc_strdup(d, "cupsui6.dll"); + d->info8.help_file = talloc_strdup(d, "pscript.hlp"); + + a = talloc_zero(d, struct spoolss_StringArray); + a->string = talloc_zero_array(a, const char *, 3); + a->string[0] = talloc_strdup(a->string, "cups6.inf"); + a->string[1] = talloc_strdup(a->string, "cups6.ini"); + + d->info8.dependent_files = a; + + torture_assert(tctx, + fillup_printserver_info(tctx, p, d), + "failed to fillup printserver info"); + + if (!directory_exist(d->local.driver_directory)) { + torture_skip(tctx, "Skipping Printer Driver test as no local driver is available"); + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d), + "failed to upload printer driver"); + + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d->info8, add_flags, true, NULL), + "failed to add driver"); + + torture_assert(tctx, + test_DeletePrinterDriverEx(tctx, b, server_name_slash, + d->info8.driver_name, + d->info8.architecture, + delete_flags, + d->info8.version), + "failed to delete driver"); + + torture_assert(tctx, + check_printer_driver_files(tctx, dcerpc_server_name(p), d, false), + "printer driver file check failed"); + + talloc_free(d); + return true; +} + +static bool test_del_driver_unused_files(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_driver_context *d1; + struct torture_driver_context *d2; + uint32_t add_flags = APD_COPY_NEW_FILES; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *server_name_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + d1 = talloc_zero(tctx, struct torture_driver_context); + d1->ex = true; + + d1->local.environment = talloc_strdup(d1, SPOOLSS_ARCHITECTURE_x64); + d1->local.driver_directory = talloc_strdup(d1, "/usr/share/cups/drivers/x64"); + + d1->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d1->info8.driver_name = TORTURE_DRIVER_DELETER; + d1->info8.architecture = NULL; + d1->info8.driver_path = talloc_strdup(d1, "pscript5.dll"); + d1->info8.data_file = talloc_strdup(d1, "cups6.ppd"); + d1->info8.config_file = talloc_strdup(d1, "cupsui6.dll"); + d1->info8.help_file = talloc_strdup(d1, "pscript.hlp"); + d1->info8.architecture = d1->local.environment; + + d2 = talloc_zero(tctx, struct torture_driver_context); + d2->ex = true; + + d2->local.environment = talloc_strdup(d2, SPOOLSS_ARCHITECTURE_x64); + d2->local.driver_directory = talloc_strdup(d2, "/usr/share/cups/drivers/x64"); + + d2->info8.version = SPOOLSS_DRIVER_VERSION_200X; + d2->info8.driver_name = TORTURE_DRIVER_DELETERIN; + d2->info8.architecture = NULL; + d2->info8.driver_path = talloc_strdup(d2, "pscript5.dll"); /* overlapping */ + d2->info8.data_file = talloc_strdup(d2, "cupsps6.dll"); + d2->info8.config_file = talloc_strdup(d2, "cups6.ini"); + d2->info8.help_file = talloc_strdup(d2, "pscript.hlp"); /* overlapping */ + d2->info8.architecture = d2->local.environment; + + torture_assert(tctx, + fillup_printserver_info(tctx, p, d1), + "failed to fillup printserver info"); + torture_assert(tctx, + fillup_printserver_info(tctx, p, d2), + "failed to fillup printserver info"); + + if (!directory_exist(d1->local.driver_directory)) { + torture_skip(tctx, "Skipping Printer Driver test as no local driver is available"); + } + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d1), + "failed to upload printer driver"); + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d1->info8, add_flags, true, NULL), + "failed to add driver"); + + torture_assert(tctx, + upload_printer_driver(tctx, dcerpc_server_name(p), d2), + "failed to upload printer driver"); + torture_assert(tctx, + test_AddPrinterDriver_args_level_3(tctx, b, server_name_slash, &d2->info8, add_flags, true, NULL), + "failed to add driver"); + + /* some files are in use by a separate driver, should fail */ + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash, + d1->info8.driver_name, + d1->info8.architecture, + DPD_DELETE_ALL_FILES, + d1->info8.version, + WERR_PRINTER_DRIVER_IN_USE), + "invalid delete driver response"); + + /* should only delete files not in use by other driver */ + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash, + d1->info8.driver_name, + d1->info8.architecture, + DPD_DELETE_UNUSED_FILES, + d1->info8.version, + WERR_OK), + "failed to delete driver (unused files)"); + + /* check non-overlapping were deleted */ + d1->info8.driver_path = NULL; + d1->info8.help_file = NULL; + torture_assert(tctx, + check_printer_driver_files(tctx, dcerpc_server_name(p), d1, false), + "printer driver file check failed"); + /* d2 files should be uneffected */ + torture_assert(tctx, + check_printer_driver_files(tctx, dcerpc_server_name(p), d2, true), + "printer driver file check failed"); + + torture_assert(tctx, + test_DeletePrinterDriverEx_exp(tctx, b, server_name_slash, + d2->info8.driver_name, + d2->info8.architecture, + DPD_DELETE_ALL_FILES, + d2->info8.version, + WERR_OK), + "failed to delete driver"); + + torture_assert(tctx, + check_printer_driver_files(tctx, dcerpc_server_name(p), d2, false), + "printer driver file check failed"); + + talloc_free(d1); + talloc_free(d2); + return true; +} + +struct torture_suite *torture_rpc_spoolss_driver(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.driver"); + + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, + "driver", &ndr_table_spoolss); + torture_rpc_tcase_add_test(tcase, "add_driver_64", test_add_driver_64); + torture_rpc_tcase_add_test(tcase, "add_driver_ex_64", test_add_driver_ex_64); + + torture_rpc_tcase_add_test(tcase, "add_driver_32", test_add_driver_32); + torture_rpc_tcase_add_test(tcase, "add_driver_ex_32", test_add_driver_ex_32); + + torture_rpc_tcase_add_test(tcase, "add_driver_adobe", test_add_driver_adobe); + + torture_rpc_tcase_add_test(tcase, "add_driver_adobe_cupsaddsmb", test_add_driver_adobe_cupsaddsmb); + + torture_rpc_tcase_add_test(tcase, "add_driver_timestamps", test_add_driver_timestamps); + + torture_rpc_tcase_add_test(tcase, "multiple_drivers", test_multiple_drivers); + + torture_rpc_tcase_add_test(tcase, + "test_driver_copy_from_directory_64", + test_driver_copy_from_directory_64); + + torture_rpc_tcase_add_test(tcase, + "test_driver_copy_from_directory_32", + test_driver_copy_from_directory_32); + + torture_rpc_tcase_add_test(tcase, "del_driver_all_files", test_del_driver_all_files); + + torture_rpc_tcase_add_test(tcase, "del_driver_unused_files", test_del_driver_unused_files); + + return suite; +} diff --git a/source4/torture/rpc/spoolss_access.c b/source4/torture/rpc/spoolss_access.c new file mode 100644 index 0000000..b0d5265 --- /dev/null +++ b/source4/torture/rpc/spoolss_access.c @@ -0,0 +1,905 @@ +/* + Unix SMB/CIFS implementation. + test suite for spoolss rpc operations + + Copyright (C) Guenther Deschner 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 "torture/torture.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "lib/cmdline/cmdline.h" + +#define TORTURE_USER "torture_user" +#define TORTURE_USER_ADMINGROUP "torture_user_544" +#define TORTURE_USER_PRINTOPGROUP "torture_user_550" +#define TORTURE_USER_PRINTOPPRIV "torture_user_priv" +#define TORTURE_USER_SD "torture_user_sd" +#define TORTURE_WORKSTATION "torture_workstation" + +struct torture_user { + const char *username; + void *testuser; + uint32_t *builtin_memberships; + uint32_t num_builtin_memberships; + const char **privs; + uint32_t num_privs; + bool privs_present; + bool sd; + bool admin_rights; + bool system_security; +}; + +struct torture_access_context { + struct dcerpc_pipe *spoolss_pipe; + const char *printername; + struct security_descriptor *sd_orig; + struct torture_user user; +}; + +static bool test_openprinter_handle(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + const char *printername, + const char *username, + uint32_t access_mask, + WERROR expected_result, + struct policy_handle *handle) +{ + struct spoolss_OpenPrinterEx r; + struct spoolss_UserLevel1 level1; + struct dcerpc_binding_handle *b = p->binding_handle; + + level1.size = 28; + level1.client = talloc_asprintf(tctx, "\\\\%s", "smbtorture"); + level1.user = username; + /* Windows 7 and Windows Server 2008 R2 */ + level1.build = 7007; + level1.major = 6; + level1.minor = 1; + level1.processor= 0; + + r.in.printername = printername; + r.in.datatype = NULL; + r.in.devmode_ctr.devmode= NULL; + r.in.access_mask = access_mask; + r.in.userlevel_ctr.level = 1; + r.in.userlevel_ctr.user_info.level1 = &level1; + r.out.handle = handle; + + torture_comment(tctx, "Testing OpenPrinterEx(%s) with access_mask 0x%08x (%s)\n", + r.in.printername, r.in.access_mask, name); + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &r), + "OpenPrinterEx failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + talloc_asprintf(tctx, "OpenPrinterEx(%s) as '%s' with access_mask: 0x%08x (%s) failed", + r.in.printername, username, r.in.access_mask, name)); + + return true; +} + +static bool test_openprinter_access(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name, + const char *printername, + const char *username, + uint32_t access_mask, + WERROR expected_result) +{ + struct policy_handle handle; + struct dcerpc_binding_handle *b = p->binding_handle; + bool ret = true; + + ZERO_STRUCT(handle); + + if (printername == NULL) { + torture_comment(tctx, "skipping test %s as there is no printer\n", name); + return true; + } + + ret = test_openprinter_handle(tctx, p, name, printername, username, access_mask, expected_result, &handle); + if (is_valid_policy_hnd(&handle)) { + test_ClosePrinter(tctx, b, &handle); + } + + return ret; +} + +static bool spoolss_access_setup_membership(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint32_t num_members, + uint32_t *members, + struct dom_sid *user_sid) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle connect_handle, domain_handle; + int i; + + torture_comment(tctx, + "Setting up BUILTIN membership for %s\n", + dom_sid_string(tctx, user_sid)); + for (i=0; i < num_members; i++) { + torture_comment(tctx, "adding user to S-1-5-32-%d\n", members[i]); + } + + { + struct samr_Connect2 r; + r.in.system_name = ""; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.connect_handle = &connect_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_Connect2_r(b, tctx, &r), + "samr_Connect2 failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_Connect2 failed"); + } + + { + struct samr_OpenDomain r; + r.in.connect_handle = &connect_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.sid = dom_sid_parse_talloc(tctx, "S-1-5-32"); + r.out.domain_handle = &domain_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenDomain_r(b, tctx, &r), + "samr_OpenDomain failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_OpenDomain failed"); + } + + for (i=0; i < num_members; i++) { + + struct policy_handle alias_handle; + + { + struct samr_OpenAlias r; + r.in.domain_handle = &domain_handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = members[i]; + r.out.alias_handle = &alias_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_OpenAlias_r(b, tctx, &r), + "samr_OpenAlias failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_OpenAlias failed"); + } + + { + struct samr_AddAliasMember r; + r.in.alias_handle = &alias_handle; + r.in.sid = user_sid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_samr_AddAliasMember_r(b, tctx, &r), + "samr_AddAliasMember failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "samr_AddAliasMember failed"); + } + + test_samr_handle_Close(b, tctx, &alias_handle); + } + + test_samr_handle_Close(b, tctx, &domain_handle); + test_samr_handle_Close(b, tctx, &connect_handle); + + return true; +} + +static void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s) +{ + name->string = s; +} +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; +} + +static bool spoolss_access_setup_privs(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint32_t num_privs, + const char **privs, + struct dom_sid *user_sid, + bool *privs_present) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle *handle; + int i; + + torture_assert(tctx, + test_lsa_OpenPolicy2(b, tctx, &handle), + "failed to open policy"); + + for (i=0; i < num_privs; i++) { + struct lsa_LookupPrivValue r; + struct lsa_LUID luid; + struct lsa_String name; + + init_lsa_String(&name, privs[i]); + + r.in.handle = handle; + r.in.name = &name; + r.out.luid = &luid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_LookupPrivValue_r(b, tctx, &r), + "lsa_LookupPrivValue failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "lsa_LookupPrivValue failed for '%s' with %s\n", + privs[i], nt_errstr(r.out.result)); + *privs_present = false; + return true; + } + } + + *privs_present = true; + + { + struct lsa_AddAccountRights r; + struct lsa_RightSet rights; + + rights.count = num_privs; + rights.names = talloc_zero_array(tctx, struct lsa_StringLarge, rights.count); + + for (i=0; i < rights.count; i++) { + init_lsa_StringLarge(&rights.names[i], privs[i]); + } + + r.in.handle = handle; + r.in.sid = user_sid; + r.in.rights = &rights; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_AddAccountRights_r(b, tctx, &r), + "lsa_AddAccountRights failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "lsa_AddAccountRights failed"); + } + + test_lsa_Close(b, tctx, handle); + + return true; +} + +static bool test_SetPrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct spoolss_SetPrinterInfoCtr *info_ctr, + struct spoolss_DevmodeContainer *devmode_ctr, + struct sec_desc_buf *secdesc_ctr, + enum spoolss_PrinterControl command) +{ + struct spoolss_SetPrinter r; + + r.in.handle = handle; + r.in.info_ctr = info_ctr; + r.in.devmode_ctr = devmode_ctr; + r.in.secdesc_ctr = secdesc_ctr; + r.in.command = command; + + torture_comment(tctx, "Testing SetPrinter level %d\n", r.in.info_ctr->level); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), + "failed to call SetPrinter"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to call SetPrinter"); + + return true; +} + +static bool spoolss_access_setup_sd(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *printername, + const struct dom_sid *user_sid, + struct security_descriptor **sd_orig) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle handle; + union spoolss_PrinterInfo info; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo3 info3; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct security_ace *ace; + struct security_descriptor *sd; + + torture_assert(tctx, + test_openprinter_handle(tctx, p, "", printername, "", SEC_FLAG_MAXIMUM_ALLOWED, WERR_OK, &handle), + "failed to open printer"); + + torture_assert(tctx, + test_GetPrinter_level(tctx, b, &handle, 3, &info), + "failed to get sd"); + + sd = security_descriptor_copy(tctx, info.info3.secdesc); + *sd_orig = security_descriptor_copy(tctx, info.info3.secdesc); + + ace = talloc_zero(tctx, struct security_ace); + + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->flags = 0; + ace->access_mask = PRINTER_ALL_ACCESS; + ace->trustee = *user_sid; + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_add(sd, ace), + "failed to add new ace"); + + ace = talloc_zero(tctx, struct security_ace); + + ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED; + ace->flags = SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_INHERIT_ONLY; + ace->access_mask = SEC_GENERIC_ALL; + ace->trustee = *user_sid; + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_add(sd, ace), + "failed to add new ace"); + + ZERO_STRUCT(info3); + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + info_ctr.level = 3; + info_ctr.info.info3 = &info3; + secdesc_ctr.sd = sd; + + torture_assert(tctx, + test_SetPrinter(tctx, b, &handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), + "failed to set sd"); + + return true; +} + +static bool test_EnumPrinters_findone(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char **printername) +{ + struct spoolss_EnumPrinters r; + uint32_t count; + union spoolss_PrinterInfo *info; + uint32_t needed; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + *printername = NULL; + + r.in.flags = PRINTER_ENUM_LOCAL; + r.in.server = NULL; + r.in.level = 1; + r.in.buffer = NULL; + r.in.offered = 0; + r.out.count = &count; + r.out.info = &info; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "failed to enum printers"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + DATA_BLOB blob = data_blob_talloc_zero(tctx, needed); + r.in.buffer = &blob; + r.in.offered = needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_EnumPrinters_r(b, tctx, &r), + "failed to enum printers"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "failed to enum printers"); + + for (i=0; i < count; i++) { + + if (count > 1 && strequal(info[i].info1.name, "Microsoft XPS Document Writer")) { + continue; + } + + torture_comment(tctx, "testing printer: %s\n", + info[i].info1.name); + + *printername = talloc_strdup(tctx, info[i].info1.name); + + break; + } + + return true; +} + +static bool torture_rpc_spoolss_access_setup_common(struct torture_context *tctx, struct torture_access_context *t) +{ + void *testuser; + const char *testuser_passwd; + struct cli_credentials *test_credentials; + struct dom_sid *test_sid; + struct dcerpc_pipe *p; + const char *printername; + const char *binding = torture_setting_string(tctx, "binding", NULL); + struct dcerpc_pipe *spoolss_pipe; + + testuser = torture_create_testuser_max_pwlen(tctx, t->user.username, + torture_setting_string(tctx, "workgroup", NULL), + ACB_NORMAL, + &testuser_passwd, + 32); + if (!testuser) { + torture_fail(tctx, "Failed to create test user"); + } + + test_credentials = cli_credentials_init(tctx); + cli_credentials_set_workstation(test_credentials, "localhost", CRED_SPECIFIED); + cli_credentials_set_domain(test_credentials, lpcfg_workgroup(tctx->lp_ctx), + CRED_SPECIFIED); + cli_credentials_set_username(test_credentials, t->user.username, CRED_SPECIFIED); + cli_credentials_set_password(test_credentials, testuser_passwd, CRED_SPECIFIED); + test_sid = discard_const_p(struct dom_sid, + torture_join_user_sid(testuser)); + + if (t->user.num_builtin_memberships) { + struct dcerpc_pipe *samr_pipe = torture_join_samr_pipe(testuser); + + torture_assert(tctx, + spoolss_access_setup_membership(tctx, samr_pipe, + t->user.num_builtin_memberships, + t->user.builtin_memberships, + test_sid), + "failed to setup membership"); + } + + if (t->user.num_privs) { + struct dcerpc_pipe *lsa_pipe; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &lsa_pipe, &ndr_table_lsarpc), + "Error connecting to server"); + + torture_assert(tctx, + spoolss_access_setup_privs(tctx, lsa_pipe, + t->user.num_privs, + t->user.privs, + test_sid, + &t->user.privs_present), + "failed to setup privs"); + talloc_free(lsa_pipe); + } + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &spoolss_pipe, &ndr_table_spoolss), + "Error connecting to server"); + + torture_assert(tctx, + test_EnumPrinters_findone(tctx, spoolss_pipe, &printername), + "failed to enumerate printers"); + + if (t->user.sd && printername) { + torture_assert(tctx, + spoolss_access_setup_sd(tctx, spoolss_pipe, + printername, + test_sid, + &t->sd_orig), + "failed to setup sd"); + } + + talloc_free(spoolss_pipe); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect(tctx, &p, binding, &ndr_table_spoolss, + test_credentials, tctx->ev, tctx->lp_ctx), + "Error connecting to server"); + + t->spoolss_pipe = p; + t->printername = printername; + t->user.testuser = testuser; + + return true; +} + +static bool torture_rpc_spoolss_access_setup(struct torture_context *tctx, void **data) +{ + struct torture_access_context *t; + + *data = t = talloc_zero(tctx, struct torture_access_context); + + t->user.username = talloc_strdup(t, TORTURE_USER); + + return torture_rpc_spoolss_access_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_access_admin_setup(struct torture_context *tctx, void **data) +{ + struct torture_access_context *t; + + *data = t = talloc_zero(tctx, struct torture_access_context); + + t->user.num_builtin_memberships = 1; + t->user.builtin_memberships = talloc_zero_array(t, uint32_t, t->user.num_builtin_memberships); + t->user.builtin_memberships[0] = BUILTIN_RID_ADMINISTRATORS; + t->user.username = talloc_strdup(t, TORTURE_USER_ADMINGROUP); + t->user.admin_rights = true; + t->user.system_security = true; + + return torture_rpc_spoolss_access_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_access_printop_setup(struct torture_context *tctx, void **data) +{ + struct torture_access_context *t; + + *data = t = talloc_zero(tctx, struct torture_access_context); + + t->user.num_builtin_memberships = 1; + t->user.builtin_memberships = talloc_zero_array(t, uint32_t, t->user.num_builtin_memberships); + t->user.builtin_memberships[0] = BUILTIN_RID_PRINT_OPERATORS; + t->user.username = talloc_strdup(t, TORTURE_USER_PRINTOPGROUP); + t->user.admin_rights = true; + + return torture_rpc_spoolss_access_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_access_priv_setup(struct torture_context *tctx, void **data) +{ + struct torture_access_context *t; + + *data = t = talloc_zero(tctx, struct torture_access_context); + + t->user.username = talloc_strdup(t, TORTURE_USER_PRINTOPPRIV); + t->user.num_privs = 1; + t->user.privs = talloc_zero_array(t, const char *, t->user.num_privs); + t->user.privs[0] = talloc_strdup(t, "SePrintOperatorPrivilege"); + t->user.admin_rights = true; + + return torture_rpc_spoolss_access_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_access_sd_setup(struct torture_context *tctx, void **data) +{ + struct torture_access_context *t; + + *data = t = talloc_zero(tctx, struct torture_access_context); + + t->user.username = talloc_strdup(t, TORTURE_USER_SD); + t->user.sd = true; + t->user.admin_rights = true; + + return torture_rpc_spoolss_access_setup_common(tctx, t); +} + +static bool torture_rpc_spoolss_access_teardown_common(struct torture_context *tctx, struct torture_access_context *t) +{ + if (t->user.testuser) { + torture_leave_domain(tctx, t->user.testuser); + } + + /* remove membership ? */ + if (t->user.num_builtin_memberships) { + } + + /* remove privs ? */ + if (t->user.num_privs) { + } + + /* restore sd */ + if (t->user.sd && t->printername) { + struct policy_handle handle; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_SetPrinterInfo3 info3; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct dcerpc_pipe *spoolss_pipe; + struct dcerpc_binding_handle *b; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection(tctx, &spoolss_pipe, &ndr_table_spoolss), + "Error connecting to server"); + + b = spoolss_pipe->binding_handle; + + ZERO_STRUCT(info_ctr); + ZERO_STRUCT(info3); + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + info_ctr.level = 3; + info_ctr.info.info3 = &info3; + secdesc_ctr.sd = t->sd_orig; + + torture_assert(tctx, + test_openprinter_handle(tctx, spoolss_pipe, "", t->printername, "", SEC_FLAG_MAXIMUM_ALLOWED, WERR_OK, &handle), + "failed to open printer"); + + torture_assert(tctx, + test_SetPrinter(tctx, b, &handle, &info_ctr, &devmode_ctr, &secdesc_ctr, 0), + "failed to set sd"); + + talloc_free(spoolss_pipe); + } + + return true; +} + +static bool torture_rpc_spoolss_access_teardown(struct torture_context *tctx, void *data) +{ + struct torture_access_context *t = talloc_get_type(data, struct torture_access_context); + bool ret; + + ret = torture_rpc_spoolss_access_teardown_common(tctx, t); + talloc_free(t); + + return ret; +} + +static bool test_openprinter(struct torture_context *tctx, + void *private_data) +{ + struct torture_access_context *t = + (struct torture_access_context *)talloc_get_type_abort(private_data, struct torture_access_context); + struct dcerpc_pipe *p = t->spoolss_pipe; + bool ret = true; + const char *username = t->user.username; + const char *servername_slash = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + int i; + + struct { + const char *name; + uint32_t access_mask; + const char *printername; + WERROR expected_result; + } checks[] = { + + /* printserver handle tests */ + + { + .name = "0", + .access_mask = 0, + .printername = servername_slash, + .expected_result = WERR_OK + }, + { + .name = "SEC_FLAG_MAXIMUM_ALLOWED", + .access_mask = SEC_FLAG_MAXIMUM_ALLOWED, + .printername = servername_slash, + .expected_result = WERR_OK + }, + { + .name = "SERVER_ACCESS_ENUMERATE", + .access_mask = SERVER_ACCESS_ENUMERATE, + .printername = servername_slash, + .expected_result = WERR_OK + }, + { + .name = "SERVER_ACCESS_ADMINISTER", + .access_mask = SERVER_ACCESS_ADMINISTER, + .printername = servername_slash, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "SERVER_READ", + .access_mask = SERVER_READ, + .printername = servername_slash, + .expected_result = WERR_OK + }, + { + .name = "SERVER_WRITE", + .access_mask = SERVER_WRITE, + .printername = servername_slash, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "SERVER_EXECUTE", + .access_mask = SERVER_EXECUTE, + .printername = servername_slash, + .expected_result = WERR_OK + }, + { + .name = "SERVER_ALL_ACCESS", + .access_mask = SERVER_ALL_ACCESS, + .printername = servername_slash, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + + /* printer handle tests */ + + { + .name = "0", + .access_mask = 0, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "SEC_FLAG_MAXIMUM_ALLOWED", + .access_mask = SEC_FLAG_MAXIMUM_ALLOWED, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "SEC_FLAG_SYSTEM_SECURITY", + .access_mask = SEC_FLAG_SYSTEM_SECURITY, + .printername = t->printername, + .expected_result = t->user.system_security ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "0x010e0000", + .access_mask = 0x010e0000, + .printername = t->printername, + .expected_result = t->user.system_security ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "PRINTER_ACCESS_USE", + .access_mask = PRINTER_ACCESS_USE, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "SEC_STD_READ_CONTROL", + .access_mask = SEC_STD_READ_CONTROL, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "PRINTER_ACCESS_ADMINISTER", + .access_mask = PRINTER_ACCESS_ADMINISTER, + .printername = t->printername, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "SEC_STD_WRITE_DAC", + .access_mask = SEC_STD_WRITE_DAC, + .printername = t->printername, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "SEC_STD_WRITE_OWNER", + .access_mask = SEC_STD_WRITE_OWNER, + .printername = t->printername, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "PRINTER_READ", + .access_mask = PRINTER_READ, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "PRINTER_WRITE", + .access_mask = PRINTER_WRITE, + .printername = t->printername, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + }, + { + .name = "PRINTER_EXECUTE", + .access_mask = PRINTER_EXECUTE, + .printername = t->printername, + .expected_result = WERR_OK + }, + { + .name = "PRINTER_ALL_ACCESS", + .access_mask = PRINTER_ALL_ACCESS, + .printername = t->printername, + .expected_result = t->user.admin_rights ? WERR_OK : WERR_ACCESS_DENIED + } + }; + + if (t->user.num_privs && !t->user.privs_present) { + torture_skip(tctx, "skipping test as not all required privileges are present on the server\n"); + } + + for (i=0; i < ARRAY_SIZE(checks); i++) { + ret &= test_openprinter_access(tctx, p, + checks[i].name, + checks[i].printername, + username, + checks[i].access_mask, + checks[i].expected_result); + } + + return ret; +} + +static bool test_openprinter_wrap(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct torture_access_context *t; + const char *printername; + bool ret = true; + + t = talloc_zero(tctx, struct torture_access_context); + + t->user.username = talloc_strdup(tctx, "dummy"); + t->spoolss_pipe = p; + + torture_assert(tctx, + test_EnumPrinters_findone(tctx, p, &printername), + "failed to enumerate printers"); + + t->printername = printername; + + ret = test_openprinter(tctx, (void *)t); + + talloc_free(t); + + return ret; +} + +struct torture_suite *torture_rpc_spoolss_access(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.access"); + struct torture_tcase *tcase; + struct torture_rpc_tcase *rpc_tcase; + + tcase = torture_suite_add_tcase(suite, "normaluser"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_access_setup, + torture_rpc_spoolss_access_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter); + + tcase = torture_suite_add_tcase(suite, "adminuser"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_access_admin_setup, + torture_rpc_spoolss_access_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter); + + tcase = torture_suite_add_tcase(suite, "printopuser"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_access_printop_setup, + torture_rpc_spoolss_access_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter); + + tcase = torture_suite_add_tcase(suite, "printopuserpriv"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_access_priv_setup, + torture_rpc_spoolss_access_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter); + + tcase = torture_suite_add_tcase(suite, "normaluser_sd"); + + torture_tcase_set_fixture(tcase, + torture_rpc_spoolss_access_sd_setup, + torture_rpc_spoolss_access_teardown); + + torture_tcase_add_simple_test(tcase, "openprinter", test_openprinter); + + rpc_tcase = torture_suite_add_machine_workstation_rpc_iface_tcase(suite, "workstation", + &ndr_table_spoolss, + TORTURE_WORKSTATION); + + torture_rpc_tcase_add_test(rpc_tcase, "openprinter", test_openprinter_wrap); + + return suite; +} diff --git a/source4/torture/rpc/spoolss_notify.c b/source4/torture/rpc/spoolss_notify.c new file mode 100644 index 0000000..bd9dac4 --- /dev/null +++ b/source4/torture/rpc/spoolss_notify.c @@ -0,0 +1,636 @@ +/* + Unix SMB/CIFS implementation. + test suite for spoolss rpc notify operations + + Copyright (C) Jelmer Vernooij 2007 + Copyright (C) Guenther Deschner 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "torture/rpc/torture_rpc.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/dcerpc_server_proto.h" +#include "rpc_server/service_rpc.h" +#include "samba/process_model.h" +#include "smb_server/smb_server.h" +#include "lib/socket/netif.h" +#include "ntvfs/ntvfs.h" +#include "param/param.h" + +static struct dcesrv_context_callbacks srv_cb = { + .log.successful_authz = log_successful_dcesrv_authz_event, + .auth.gensec_prepare = dcesrv_gensec_prepare, + .assoc_group.find = dcesrv_assoc_group_find_s4, +}; + +static NTSTATUS spoolss__op_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return NT_STATUS_OK; +} + +static void spoolss__op_unbind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface) +{ +} + +static NTSTATUS spoolss__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; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + dce_call->fault_code = 0; + + if (opnum >= ndr_table_spoolss.num_calls) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + + *r = talloc_size(mem_ctx, ndr_table_spoolss.calls[opnum].struct_size); + NT_STATUS_HAVE_NO_MEMORY(*r); + + /* unravel the NDR for the packet */ + ndr_err = ndr_table_spoolss.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; +} + +/* Note that received_packets are allocated on the NULL context + * because no other context appears to stay around long enough. */ +static struct received_packet { + uint16_t opnum; + void *r; + struct received_packet *prev, *next; +} *received_packets = NULL; + +static void free_received_packets(void) +{ + struct received_packet *rp; + struct received_packet *rp_next; + + for (rp = received_packets; rp; rp = rp_next) { + rp_next = rp->next; + DLIST_REMOVE(received_packets, rp); + talloc_unlink(rp, rp->r); + talloc_free(rp); + } + received_packets = NULL; +} + +static WERROR _spoolss_ReplyOpenPrinter(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_ReplyOpenPrinter *r) +{ + DEBUG(1,("_spoolss_ReplyOpenPrinter\n")); + + NDR_PRINT_IN_DEBUG(spoolss_ReplyOpenPrinter, r); + + r->out.handle = talloc(r, struct policy_handle); + r->out.handle->handle_type = 42; + r->out.handle->uuid = GUID_random(); + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_ReplyOpenPrinter, r); + + return WERR_OK; +} + +static WERROR _spoolss_ReplyClosePrinter(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_ReplyClosePrinter *r) +{ + DEBUG(1,("_spoolss_ReplyClosePrinter\n")); + + NDR_PRINT_IN_DEBUG(spoolss_ReplyClosePrinter, r); + + ZERO_STRUCTP(r->out.handle); + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_ReplyClosePrinter, r); + + return WERR_OK; +} + +static WERROR _spoolss_RouterReplyPrinterEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct spoolss_RouterReplyPrinterEx *r) +{ + DEBUG(1,("_spoolss_RouterReplyPrinterEx\n")); + + NDR_PRINT_IN_DEBUG(spoolss_RouterReplyPrinterEx, r); + + r->out.reply_result = talloc(r, uint32_t); + *r->out.reply_result = 0; + r->out.result = WERR_OK; + + NDR_PRINT_OUT_DEBUG(spoolss_RouterReplyPrinterEx, r); + + return WERR_OK; +} + +static NTSTATUS spoolss__op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + uint16_t opnum = dce_call->pkt.u.request.opnum; + struct received_packet *rp; + + rp = talloc_zero(NULL, struct received_packet); + rp->opnum = opnum; + rp->r = talloc_reference(rp, r); + + DLIST_ADD_END(received_packets, rp); + + switch (opnum) { + case 58: { + struct spoolss_ReplyOpenPrinter *r2 = (struct spoolss_ReplyOpenPrinter *)r; + r2->out.result = _spoolss_ReplyOpenPrinter(dce_call, mem_ctx, r2); + break; + } + case 60: { + struct spoolss_ReplyClosePrinter *r2 = (struct spoolss_ReplyClosePrinter *)r; + r2->out.result = _spoolss_ReplyClosePrinter(dce_call, mem_ctx, r2); + break; + } + case 66: { + struct spoolss_RouterReplyPrinterEx *r2 = (struct spoolss_RouterReplyPrinterEx *)r; + r2->out.result = _spoolss_RouterReplyPrinterEx(dce_call, mem_ctx, r2); + break; + } + + default: + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + break; + } + + if (dce_call->fault_code != 0) { + return NT_STATUS_NET_WRITE_FAULT; + } + return NT_STATUS_OK; +} + + +static NTSTATUS spoolss__op_reply(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + return NT_STATUS_OK; +} + + +static NTSTATUS spoolss__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; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + ndr_err = ndr_table_spoolss.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 const struct dcesrv_interface notify_test_spoolss_interface = { + .name = "spoolss", + .syntax_id = {{0x12345678,0x1234,0xabcd,{0xef,0x00},{0x01,0x23,0x45,0x67,0x89,0xab}},1.0}, + .bind = spoolss__op_bind, + .unbind = spoolss__op_unbind, + .ndr_pull = spoolss__op_ndr_pull, + .dispatch = spoolss__op_dispatch, + .reply = spoolss__op_reply, + .ndr_push = spoolss__op_ndr_push +}; + +static bool spoolss__op_interface_by_uuid(struct dcesrv_interface *iface, const struct GUID *uuid, uint32_t if_version) +{ + if (notify_test_spoolss_interface.syntax_id.if_version == if_version && + GUID_equal(¬ify_test_spoolss_interface.syntax_id.uuid, uuid)) { + memcpy(iface,¬ify_test_spoolss_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static bool spoolss__op_interface_by_name(struct dcesrv_interface *iface, const char *name) +{ + if (strcmp(notify_test_spoolss_interface.name, name)==0) { + memcpy(iface, ¬ify_test_spoolss_interface, sizeof(*iface)); + return true; + } + + return false; +} + +static NTSTATUS spoolss__op_init_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server) +{ + uint32_t i; + + for (i=0;i<ndr_table_spoolss.endpoints->count;i++) { + NTSTATUS ret; + const char *name = ndr_table_spoolss.endpoints->names[i]; + + ret = dcesrv_interface_register(dce_ctx, + name, + NULL, + ¬ify_test_spoolss_interface, + NULL); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1,("spoolss_op_init_server: failed to register endpoint '%s'\n",name)); + return ret; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS spoolss__op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + return NT_STATUS_OK; +} + +static bool test_OpenPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *printername) +{ + struct spoolss_OpenPrinter r; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + + r.in.printername = printername; + r.in.datatype = NULL; + r.in.devmode_ctr.devmode= NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = handle; + + torture_comment(tctx, "Testing OpenPrinter(%s)\n", r.in.printername); + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_OpenPrinter_r(b, tctx, &r), + "OpenPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, + "OpenPrinter failed"); + + return true; +} + +static struct spoolss_NotifyOption *setup_printserver_NotifyOption(struct torture_context *tctx) +{ + struct spoolss_NotifyOption *o; + + o = talloc_zero(tctx, struct spoolss_NotifyOption); + + o->version = 2; + o->flags = PRINTER_NOTIFY_OPTIONS_REFRESH; + + o->count = 2; + o->types = talloc_zero_array(o, struct spoolss_NotifyOptionType, o->count); + + o->types[0].type = PRINTER_NOTIFY_TYPE; + o->types[0].count = 1; + o->types[0].fields = talloc_array(o->types, union spoolss_Field, o->types[0].count); + o->types[0].fields[0].field = PRINTER_NOTIFY_FIELD_SERVER_NAME; + + o->types[1].type = JOB_NOTIFY_TYPE; + o->types[1].count = 1; + o->types[1].fields = talloc_array(o->types, union spoolss_Field, o->types[1].count); + o->types[1].fields[0].field = JOB_NOTIFY_FIELD_MACHINE_NAME; + + return o; +} + +#if 0 +static struct spoolss_NotifyOption *setup_printer_NotifyOption(struct torture_context *tctx) +{ + struct spoolss_NotifyOption *o; + + o = talloc_zero(tctx, struct spoolss_NotifyOption); + + o->version = 2; + o->flags = PRINTER_NOTIFY_OPTIONS_REFRESH; + + o->count = 1; + o->types = talloc_zero_array(o, struct spoolss_NotifyOptionType, o->count); + + o->types[0].type = PRINTER_NOTIFY_TYPE; + o->types[0].count = 1; + o->types[0].fields = talloc_array(o->types, union spoolss_Field, o->types[0].count); + o->types[0].fields[0].field = PRINTER_NOTIFY_FIELD_COMMENT; + + return o; +} +#endif + +static bool test_RemoteFindFirstPrinterChangeNotifyEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *address, + struct spoolss_NotifyOption *option) +{ + struct spoolss_RemoteFindFirstPrinterChangeNotifyEx r; + const char *local_machine = talloc_asprintf(tctx, "\\\\%s", address); + + torture_comment(tctx, "Testing RemoteFindFirstPrinterChangeNotifyEx(%s)\n", local_machine); + + r.in.flags = 0; + r.in.local_machine = local_machine; + r.in.options = 0; + r.in.printer_local = 0; + r.in.notify_options = option; + r.in.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_RemoteFindFirstPrinterChangeNotifyEx_r(b, tctx, &r), + "RemoteFindFirstPrinterChangeNotifyEx failed"); + torture_assert_werr_ok(tctx, r.out.result, + "error return code for RemoteFindFirstPrinterChangeNotifyEx"); + + return true; +} + +static bool test_RouterRefreshPrinterChangeNotify(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct spoolss_NotifyOption *options, + struct spoolss_NotifyInfo **info) +{ + struct spoolss_RouterRefreshPrinterChangeNotify r; + + torture_comment(tctx, "Testing RouterRefreshPrinterChangeNotify\n"); + + r.in.handle = handle; + r.in.change_low = 0; + r.in.options = options; + r.out.info = info; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_RouterRefreshPrinterChangeNotify_r(b, tctx, &r), + "RouterRefreshPrinterChangeNotify failed"); + torture_assert_werr_ok(tctx, r.out.result, + "error return code for RouterRefreshPrinterChangeNotify"); + + return true; +} + +#if 0 +static bool test_SetPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + union spoolss_PrinterInfo info; + struct spoolss_SetPrinter r; + struct spoolss_SetPrinterInfo2 info2; + struct spoolss_SetPrinterInfoCtr info_ctr; + struct spoolss_DevmodeContainer devmode_ctr; + struct sec_desc_buf secdesc_ctr; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, test_GetPrinter_level(tctx, b, handle, 2, &info), ""); + + ZERO_STRUCT(devmode_ctr); + ZERO_STRUCT(secdesc_ctr); + + info2.servername = info.info2.servername; + info2.printername = info.info2.printername; + info2.sharename = info.info2.sharename; + info2.portname = info.info2.portname; + info2.drivername = info.info2.drivername; + info2.comment = talloc_asprintf(tctx, "torture_comment %d\n", (int)time(NULL)); + info2.location = info.info2.location; + info2.devmode_ptr = 0; + info2.sepfile = info.info2.sepfile; + info2.printprocessor = info.info2.printprocessor; + info2.datatype = info.info2.datatype; + info2.parameters = info.info2.parameters; + info2.secdesc_ptr = 0; + info2.attributes = info.info2.attributes; + info2.priority = info.info2.priority; + info2.defaultpriority = info.info2.defaultpriority; + info2.starttime = info.info2.starttime; + info2.untiltime = info.info2.untiltime; + info2.status = info.info2.status; + info2.cjobs = info.info2.cjobs; + info2.averageppm = info.info2.averageppm; + + info_ctr.level = 2; + info_ctr.info.info2 = &info2; + + r.in.handle = handle; + r.in.info_ctr = &info_ctr; + r.in.devmode_ctr = &devmode_ctr; + r.in.secdesc_ctr = &secdesc_ctr; + r.in.command = 0; + + torture_assert_ntstatus_ok(tctx, dcerpc_spoolss_SetPrinter_r(b, tctx, &r), "SetPrinter failed"); + torture_assert_werr_ok(tctx, r.out.result, "SetPrinter failed"); + + return true; +} +#endif + +static bool test_start_dcerpc_server(struct torture_context *tctx, + struct tevent_context *event_ctx, + struct dcesrv_context **dce_ctx_p, + const char **address_p) +{ + struct dcesrv_endpoint_server ep_server; + NTSTATUS status; + struct dcesrv_context *dce_ctx; + const char *endpoints[] = { "spoolss", NULL }; + struct dcesrv_endpoint *e; + const char *address; + struct interface *ifaces; + + ntvfs_init(tctx->lp_ctx); + + /* fill in our name */ + ep_server.name = "spoolss"; + + ep_server.initialized = false; + + /* fill in all the operations */ + ep_server.init_server = spoolss__op_init_server; + ep_server.shutdown_server = spoolss__op_shutdown_server; + + ep_server.interface_by_uuid = spoolss__op_interface_by_uuid; + ep_server.interface_by_name = spoolss__op_interface_by_name; + + torture_assert_ntstatus_ok(tctx, dcerpc_register_ep_server(&ep_server), + "unable to register spoolss server"); + + lpcfg_set_cmdline(tctx->lp_ctx, "dcerpc endpoint servers", "spoolss"); + + load_interface_list(tctx, tctx->lp_ctx, &ifaces); + address = iface_list_first_v4(ifaces); + + torture_comment(tctx, "Listening for callbacks on %s\n", address); + + status = process_model_init(tctx->lp_ctx); + torture_assert_ntstatus_ok(tctx, status, + "unable to initialize process models"); + + status = smbsrv_add_socket(tctx, event_ctx, tctx->lp_ctx, + process_model_startup("single"), + address, NULL); + torture_assert_ntstatus_ok(tctx, status, "starting smb server"); + + status = dcesrv_init_context(tctx, tctx->lp_ctx, &srv_cb, &dce_ctx); + torture_assert_ntstatus_ok(tctx, status, + "unable to initialize DCE/RPC server"); + + status = dcesrv_init_ep_servers(dce_ctx, endpoints); + torture_assert_ntstatus_ok(tctx, + status, + "unable to initialize DCE/RPC ep servers"); + + for (e=dce_ctx->endpoint_list;e;e=e->next) { + status = dcesrv_add_ep(dce_ctx, tctx->lp_ctx, + e, tctx->ev, + process_model_startup("single"), NULL); + torture_assert_ntstatus_ok(tctx, status, + "unable listen on dcerpc endpoint server"); + } + + *dce_ctx_p = dce_ctx; + *address_p = address; + + return true; +} + +static struct received_packet *last_packet(struct received_packet *p) +{ + struct received_packet *tmp; + for (tmp = p; tmp->next; tmp = tmp->next) { + } + return tmp; +} + +static bool test_RFFPCNEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct dcesrv_context *dce_ctx; + struct policy_handle handle; + const char *address; + struct received_packet *tmp; + struct spoolss_NotifyOption *server_option = setup_printserver_NotifyOption(tctx); +#if 0 + struct spoolss_NotifyOption *printer_option = setup_printer_NotifyOption(tctx); +#endif + struct dcerpc_binding_handle *b = p->binding_handle; + const char *printername = NULL; + struct spoolss_NotifyInfo *info = NULL; + + free_received_packets(); + + /* Start DCE/RPC server */ + torture_assert(tctx, test_start_dcerpc_server(tctx, tctx->ev, &dce_ctx, &address), ""); + + printername = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + + torture_assert(tctx, test_OpenPrinter(tctx, p, &handle, printername), ""); + torture_assert(tctx, test_RemoteFindFirstPrinterChangeNotifyEx(tctx, b, &handle, address, server_option), ""); + torture_assert(tctx, received_packets, "no packets received"); + torture_assert_int_equal(tctx, received_packets->opnum, NDR_SPOOLSS_REPLYOPENPRINTER, + "no ReplyOpenPrinter packet after RemoteFindFirstPrinterChangeNotifyEx"); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, b, &handle, NULL, &info), ""); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, b, &handle, server_option, &info), ""); + torture_assert(tctx, test_ClosePrinter(tctx, b, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYCLOSEPRINTER, + "no ReplyClosePrinter packet after ClosePrinter"); +#if 0 + printername = talloc_asprintf(tctx, "\\\\%s\\%s", dcerpc_server_name(p), name); + + torture_assert(tctx, test_OpenPrinter(tctx, p, &handle, "Epson AL-2600"), ""); + torture_assert(tctx, test_RemoteFindFirstPrinterChangeNotifyEx(tctx, p, &handle, address, printer_option), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYOPENPRINTER, + "no ReplyOpenPrinter packet after RemoteFindFirstPrinterChangeNotifyEx"); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, p, &handle, NULL, &info), ""); + torture_assert(tctx, test_RouterRefreshPrinterChangeNotify(tctx, p, &handle, printer_option, &info), ""); + torture_assert(tctx, test_SetPrinter(tctx, p, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_ROUTERREPLYPRINTEREX, + "no RouterReplyPrinterEx packet after ClosePrinter"); + torture_assert(tctx, test_ClosePrinter(tctx, p, &handle), ""); + tmp = last_packet(received_packets); + torture_assert_int_equal(tctx, tmp->opnum, NDR_SPOOLSS_REPLYCLOSEPRINTER, + "no ReplyClosePrinter packet after ClosePrinter"); +#endif + /* Shut down DCE/RPC server */ + talloc_free(dce_ctx); + free_received_packets(); + + return true; +} + +/** Test that makes sure that calling ReplyOpenPrinter() + * on Samba 4 will cause an irpc broadcast call. + */ +static bool test_ReplyOpenPrinter(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct spoolss_ReplyOpenPrinter r; + struct spoolss_ReplyClosePrinter s; + struct policy_handle h; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (torture_setting_bool(tctx, "samba3", false)) { + torture_skip(tctx, "skipping ReplyOpenPrinter server implementation test against s3\n"); + } + + r.in.server_name = "earth"; + r.in.printer_local = 2; + r.in.type = REG_DWORD; + r.in.bufsize = 0; + r.in.buffer = NULL; + r.out.handle = &h; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_ReplyOpenPrinter_r(b, tctx, &r), + "spoolss_ReplyOpenPrinter call failed"); + + torture_assert_werr_ok(tctx, r.out.result, "error return code"); + + s.in.handle = &h; + s.out.handle = &h; + + torture_assert_ntstatus_ok(tctx, + dcerpc_spoolss_ReplyClosePrinter_r(b, tctx, &s), + "spoolss_ReplyClosePrinter call failed"); + + torture_assert_werr_ok(tctx, r.out.result, "error return code"); + + return true; +} + +struct torture_suite *torture_rpc_spoolss_notify(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.notify"); + + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, + "notify", &ndr_table_spoolss); + + torture_rpc_tcase_add_test(tcase, "testRFFPCNEx", test_RFFPCNEx); + torture_rpc_tcase_add_test(tcase, "testReplyOpenPrinter", test_ReplyOpenPrinter); + + return suite; +} diff --git a/source4/torture/rpc/spoolss_win.c b/source4/torture/rpc/spoolss_win.c new file mode 100644 index 0000000..9162acd --- /dev/null +++ b/source4/torture/rpc/spoolss_win.c @@ -0,0 +1,612 @@ +/* + Unix SMB/CIFS implementation. + test suite for spoolss rpc operations as performed by various win versions + + Copyright (C) Kai Blin 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 "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_spoolss_c.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "param/param.h" + +struct test_spoolss_win_context { + /* EnumPrinters */ + uint32_t printer_count; + union spoolss_PrinterInfo *printer_info; + union spoolss_PrinterInfo *current_info; + + /* EnumPrinterKeys */ + const char **printer_keys; + + bool printer_has_driver; +}; + +/* This is a convenience function for all OpenPrinterEx calls */ +static bool test_OpenPrinterEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *printer_name, + uint32_t access_mask) +{ + NTSTATUS status; + struct spoolss_OpenPrinterEx op; + struct spoolss_UserLevel1 ul_1; + + torture_comment(tctx, "Opening printer '%s'\n", printer_name); + + op.in.printername = talloc_strdup(tctx, printer_name); + op.in.datatype = NULL; + op.in.devmode_ctr.devmode = NULL; + op.in.access_mask = access_mask; + op.in.userlevel_ctr.level = 1; + op.in.userlevel_ctr.user_info.level1 = &ul_1; + op.out.handle = handle; + + ul_1.size = 1234; + ul_1.client = "\\clientname"; + ul_1.user = "username"; + ul_1.build = 1; + ul_1.major = 2; + ul_1.minor = 3; + ul_1.processor = 4567; + + status = dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &op); + torture_assert_ntstatus_ok(tctx, status, "OpenPrinterEx failed"); + torture_assert_werr_ok(tctx, op.out.result, "OpenPrinterEx failed"); + + return true; +} + +static bool test_OpenPrinterAsAdmin(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + const char *printername) +{ + NTSTATUS status; + struct spoolss_OpenPrinterEx op; + struct spoolss_ClosePrinter cp; + struct spoolss_UserLevel1 ul_1; + struct policy_handle handle; + + ul_1.size = 1234; + ul_1.client = "\\clientname"; + ul_1.user = "username"; + ul_1.build = 1; + ul_1.major = 2; + ul_1.minor = 3; + ul_1.processor = 4567; + + op.in.printername = talloc_strdup(tctx, printername); + op.in.datatype = NULL; + op.in.devmode_ctr.devmode = NULL; + op.in.access_mask = SERVER_ALL_ACCESS; + op.in.userlevel_ctr.level = 1; + op.in.userlevel_ctr.user_info.level1 = &ul_1; + op.out.handle = &handle; + + cp.in.handle = &handle; + cp.out.handle = &handle; + + torture_comment(tctx, "Testing OpenPrinterEx(%s) with admin rights\n", + op.in.printername); + + status = dcerpc_spoolss_OpenPrinterEx_r(b, tctx, &op); + + if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(op.out.result)) { + status = dcerpc_spoolss_ClosePrinter_r(b, tctx, &cp); + torture_assert_ntstatus_ok(tctx, status, "ClosePrinter failed"); + } + + return true; +} + + + +/* This replicates the opening sequence of OpenPrinterEx calls XP does */ +static bool test_OpenPrinterSequence(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle) +{ + bool ret; + char *printername = talloc_asprintf(tctx, "\\\\%s", + dcerpc_server_name(p)); + struct dcerpc_binding_handle *b = p->binding_handle; + + /* First, see if we can open the printer read_only */ + ret = test_OpenPrinterEx(tctx, b, handle, printername, 0); + torture_assert(tctx, ret == true, "OpenPrinterEx failed."); + + ret = test_ClosePrinter(tctx, b, handle); + torture_assert(tctx, ret, "ClosePrinter failed"); + + /* Now let's see if we have admin rights to it. */ + ret = test_OpenPrinterAsAdmin(tctx, b, printername); + torture_assert(tctx, ret == true, + "OpenPrinterEx as admin failed unexpectedly."); + + ret = test_OpenPrinterEx(tctx, b, handle, printername, SERVER_EXECUTE); + torture_assert(tctx, ret == true, "OpenPrinterEx failed."); + + return true; +} + +static bool test_GetPrinterData(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *value_name, + WERROR expected_werr, + uint32_t expected_value) +{ + NTSTATUS status; + struct spoolss_GetPrinterData gpd; + uint32_t needed; + enum winreg_Type type; + uint8_t *data = talloc_zero_array(tctx, uint8_t, 4); + + torture_comment(tctx, "Testing GetPrinterData(%s).\n", value_name); + gpd.in.handle = handle; + gpd.in.value_name = value_name; + gpd.in.offered = 4; + gpd.out.needed = &needed; + gpd.out.type = &type; + gpd.out.data = data; + + status = dcerpc_spoolss_GetPrinterData_r(b, tctx, &gpd); + torture_assert_ntstatus_ok(tctx, status, "GetPrinterData failed."); + torture_assert_werr_equal(tctx, gpd.out.result, expected_werr, + "GetPrinterData did not return expected error value."); + + if (W_ERROR_IS_OK(expected_werr)) { + uint32_t value = IVAL(data, 0); + torture_assert_int_equal(tctx, value, + expected_value, + talloc_asprintf(tctx, "GetPrinterData for %s did not return expected value.", value_name)); + } + return true; +} + +static bool test_EnumPrinters(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct test_spoolss_win_context *ctx, + uint32_t initial_blob_size) +{ + NTSTATUS status; + struct spoolss_EnumPrinters ep; + DATA_BLOB blob = data_blob_talloc_zero(ctx, initial_blob_size); + uint32_t needed; + uint32_t count; + union spoolss_PrinterInfo *info; + struct dcerpc_binding_handle *b = p->binding_handle; + + ep.in.flags = PRINTER_ENUM_NAME; + ep.in.server = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + ep.in.level = 2; + ep.in.buffer = &blob; + ep.in.offered = initial_blob_size; + ep.out.needed = &needed; + ep.out.count = &count; + ep.out.info = &info; + + status = dcerpc_spoolss_EnumPrinters_r(b, ctx, &ep); + torture_assert_ntstatus_ok(tctx, status, "EnumPrinters failed."); + + if (W_ERROR_EQUAL(ep.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(ctx, needed); + ep.in.buffer = &blob; + ep.in.offered = needed; + status = dcerpc_spoolss_EnumPrinters_r(b, ctx, &ep); + torture_assert_ntstatus_ok(tctx, status,"EnumPrinters failed."); + } + + torture_assert_werr_ok(tctx, ep.out.result, "EnumPrinters failed."); + + ctx->printer_count = count; + ctx->printer_info = info; + + torture_comment(tctx, "Found %d printer(s).\n", ctx->printer_count); + + return true; +} + +static bool test_GetPrinter(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + struct test_spoolss_win_context *ctx, + uint32_t level, + uint32_t initial_blob_size) +{ + NTSTATUS status; + struct spoolss_GetPrinter gp; + DATA_BLOB blob = data_blob_talloc_zero(ctx, initial_blob_size); + uint32_t needed; + + torture_comment(tctx, "Test GetPrinter level %d\n", level); + + gp.in.handle = handle; + gp.in.level = level; + gp.in.buffer = (initial_blob_size == 0)?NULL:&blob; + gp.in.offered = initial_blob_size; + gp.out.needed = &needed; + + status = dcerpc_spoolss_GetPrinter_r(b, tctx, &gp); + torture_assert_ntstatus_ok(tctx, status, "GetPrinter failed"); + + if (W_ERROR_EQUAL(gp.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(ctx, needed); + gp.in.buffer = &blob; + gp.in.offered = needed; + status = dcerpc_spoolss_GetPrinter_r(b, tctx, &gp); + torture_assert_ntstatus_ok(tctx, status, "GetPrinter failed"); + } + + torture_assert_werr_ok(tctx, gp.out.result, "GetPrinter failed"); + + ctx->current_info = gp.out.info; + + if (level == 2 && gp.out.info) { + ctx->printer_has_driver = gp.out.info->info2.drivername && + strlen(gp.out.info->info2.drivername); + } + + return true; +} + +static bool test_EnumJobs(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_EnumJobs ej; + DATA_BLOB blob = data_blob_talloc_zero(tctx, 1024); + uint32_t needed; + uint32_t count; + union spoolss_JobInfo *info; + + torture_comment(tctx, "Test EnumJobs\n"); + + ZERO_STRUCT(ej); + ej.in.handle = handle; + ej.in.firstjob = 0; + ej.in.numjobs = 0; + ej.in.level = 2; + ej.in.buffer = &blob; + ej.in.offered = 1024; + ej.out.needed = &needed; + ej.out.count = &count; + ej.out.info = &info; + + status = dcerpc_spoolss_EnumJobs_r(b, tctx, &ej); + torture_assert_ntstatus_ok(tctx, status, "EnumJobs failed"); + if (W_ERROR_EQUAL(ej.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(tctx, needed); + ej.in.offered = needed; + ej.in.buffer = &blob; + status = dcerpc_spoolss_EnumJobs_r(b, tctx, &ej); + torture_assert_ntstatus_ok(tctx, status, "EnumJobs failed"); + } + torture_assert_werr_ok(tctx, ej.out.result, "EnumJobs failed"); + + return true; +} + +static bool test_GetPrinterDriver2(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct test_spoolss_win_context *ctx, + struct policy_handle *handle) +{ + NTSTATUS status; + struct spoolss_GetPrinterDriver2 gpd2; + DATA_BLOB blob = data_blob_talloc_zero(tctx, 87424); + uint32_t needed; + uint32_t server_major_version; + uint32_t server_minor_version; + + torture_comment(tctx, "Testing GetPrinterDriver2\n"); + + gpd2.in.handle = handle; + gpd2.in.architecture = "Windows NT x86"; + gpd2.in.level = 101; + gpd2.in.buffer = &blob; + gpd2.in.offered = 87424; + gpd2.in.client_major_version = 3; + gpd2.in.client_minor_version = 0; + gpd2.out.needed = &needed; + gpd2.out.server_major_version = &server_major_version; + gpd2.out.server_minor_version = &server_minor_version; + + status = dcerpc_spoolss_GetPrinterDriver2_r(b, tctx, &gpd2); + torture_assert_ntstatus_ok(tctx, status, "GetPrinterDriver2 failed"); + + if (ctx->printer_has_driver) { + torture_assert_werr_ok(tctx, gpd2.out.result, + "GetPrinterDriver2 failed."); + } + + return true; +} + +static bool test_EnumForms(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + uint32_t initial_blob_size) +{ + NTSTATUS status; + struct spoolss_EnumForms ef; + DATA_BLOB blob = data_blob_talloc_zero(tctx, initial_blob_size); + uint32_t needed; + uint32_t count; + union spoolss_FormInfo *info; + + torture_comment(tctx, "Testing EnumForms\n"); + + ef.in.handle = handle; + ef.in.level = 1; + ef.in.buffer = (initial_blob_size == 0)?NULL:&blob; + ef.in.offered = initial_blob_size; + ef.out.needed = &needed; + ef.out.count = &count; + ef.out.info = &info; + + status = dcerpc_spoolss_EnumForms_r(b, tctx, &ef); + torture_assert_ntstatus_ok(tctx, status, "EnumForms failed"); + + if (W_ERROR_EQUAL(ef.out.result, WERR_INSUFFICIENT_BUFFER)) { + blob = data_blob_talloc_zero(tctx, needed); + ef.in.buffer = &blob; + ef.in.offered = needed; + status = dcerpc_spoolss_EnumForms_r(b, tctx, &ef); + torture_assert_ntstatus_ok(tctx, status, "EnumForms failed"); + } + + torture_assert_werr_ok(tctx, ef.out.result, "EnumForms failed"); + + return true; +} + +static bool test_EnumPrinterKey(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char* key, + struct test_spoolss_win_context *ctx) +{ + NTSTATUS status; + struct spoolss_EnumPrinterKey epk; + uint32_t needed = 0; + union spoolss_KeyNames key_buffer; + uint32_t _ndr_size; + + torture_comment(tctx, "Testing EnumPrinterKey(%s)\n", key); + + epk.in.handle = handle; + epk.in.key_name = talloc_strdup(tctx, key); + epk.in.offered = 0; + epk.out.needed = &needed; + epk.out.key_buffer = &key_buffer; + epk.out._ndr_size = &_ndr_size; + + status = dcerpc_spoolss_EnumPrinterKey_r(b, tctx, &epk); + torture_assert_ntstatus_ok(tctx, status, "EnumPrinterKey failed"); + + if (W_ERROR_EQUAL(epk.out.result, WERR_MORE_DATA)) { + epk.in.offered = needed; + status = dcerpc_spoolss_EnumPrinterKey_r(b, tctx, &epk); + torture_assert_ntstatus_ok(tctx, status, + "EnumPrinterKey failed"); + } + + torture_assert_werr_ok(tctx, epk.out.result, "EnumPrinterKey failed"); + + ctx->printer_keys = key_buffer.string_array; + + return true; +} + +static bool test_EnumPrinterDataEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key, + uint32_t initial_blob_size, + WERROR expected_error) +{ + NTSTATUS status; + struct spoolss_EnumPrinterDataEx epde; + struct spoolss_PrinterEnumValues *info; + uint32_t needed; + uint32_t count; + + torture_comment(tctx, "Testing EnumPrinterDataEx(%s)\n", key); + + epde.in.handle = handle; + epde.in.key_name = talloc_strdup(tctx, key); + epde.in.offered = 0; + epde.out.needed = &needed; + epde.out.count = &count; + epde.out.info = &info; + + status = dcerpc_spoolss_EnumPrinterDataEx_r(b, tctx, &epde); + torture_assert_ntstatus_ok(tctx, status, "EnumPrinterDataEx failed."); + if (W_ERROR_EQUAL(epde.out.result, WERR_MORE_DATA)) { + epde.in.offered = needed; + status = dcerpc_spoolss_EnumPrinterDataEx_r(b, tctx, &epde); + torture_assert_ntstatus_ok(tctx, status, + "EnumPrinterDataEx failed."); + } + + torture_assert_werr_equal(tctx, epde.out.result, expected_error, + "EnumPrinterDataEx failed."); + + return true; +} + +static bool test_WinXP(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + bool ret = true; + struct test_spoolss_win_context *ctx, *tmp_ctx; + struct policy_handle handle01, handle02, handle03, handle04; + /* Sometimes a handle stays unused. In order to make this clear in the + * code, the unused_handle structures are used for that. */ + struct policy_handle unused_handle1, unused_handle2; + char *server_name; + uint32_t i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ctx = talloc_zero(tctx, struct test_spoolss_win_context); + tmp_ctx = talloc_zero(tctx, struct test_spoolss_win_context); + + ret &= test_OpenPrinterSequence(tctx, p, &handle01); + ret &= test_GetPrinterData(tctx, b, &handle01,"UISingleJobStatusString", + WERR_INVALID_PARAMETER, 0); + torture_comment(tctx, "Skip RemoteFindNextPrinterChangeNotifyEx test\n"); + + server_name = talloc_asprintf(ctx, "\\\\%s", dcerpc_server_name(p)); + ret &= test_OpenPrinterEx(tctx, b, &unused_handle1, server_name, 0); + + ret &= test_EnumPrinters(tctx, p, ctx, 1024); + + ret &= test_OpenPrinterEx(tctx, b, &handle02, server_name, 0); + ret &= test_GetPrinterData(tctx, b, &handle02, "MajorVersion", WERR_OK, + 3); + ret &= test_ClosePrinter(tctx, b, &handle02); + + /* If no printers were found, skip all tests that need a printer */ + if (ctx->printer_count == 0) { + goto end_testWinXP; + } + + ret &= test_OpenPrinterEx(tctx, b, &handle02, + ctx->printer_info[0].info2.printername, + PRINTER_ACCESS_USE); + ret &= test_GetPrinter(tctx, b, &handle02, ctx, 2, 0); + + torture_assert_str_equal(tctx, ctx->current_info->info2.printername, + ctx->printer_info[0].info2.printername, + "GetPrinter returned unexpected printername"); + /*FIXME: Test more components of the PrinterInfo2 struct */ + + ret &= test_OpenPrinterEx(tctx, b, &handle03, + ctx->printer_info[0].info2.printername, 0); + ret &= test_GetPrinter(tctx, b, &handle03, ctx, 0, 1164); + ret &= test_GetPrinter(tctx, b, &handle03, ctx, 2, 0); + + ret &= test_OpenPrinterEx(tctx, b, &handle04, + ctx->printer_info[0].info2.printername, 0); + ret &= test_GetPrinter(tctx, b, &handle04, ctx, 2, 0); + ret &= test_ClosePrinter(tctx, b, &handle04); + + ret &= test_OpenPrinterEx(tctx, b, &handle04, + ctx->printer_info[0].info2.printername, 0); + ret &= test_GetPrinter(tctx, b, &handle04, ctx, 2, 4096); + ret &= test_ClosePrinter(tctx, b, &handle04); + + ret &= test_OpenPrinterAsAdmin(tctx, b, + ctx->printer_info[0].info2.printername); + + ret &= test_OpenPrinterEx(tctx, b, &handle04, + ctx->printer_info[0].info2.printername, PRINTER_READ); + ret &= test_GetPrinterData(tctx, b, &handle04,"UISingleJobStatusString", + WERR_FILE_NOT_FOUND, 0); + torture_comment(tctx, "Skip RemoteFindNextPrinterChangeNotifyEx test\n"); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + + ret &= test_EnumJobs(tctx, b, &handle04); + ret &= test_GetPrinter(tctx, b, &handle04, ctx, 2, 4096); + + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + ret &= test_ClosePrinter(tctx, b, &handle04); + + ret &= test_EnumPrinters(tctx, p, ctx, 1556); + ret &= test_GetPrinterDriver2(tctx, b, ctx, &handle03); + ret &= test_EnumForms(tctx, b, &handle03, 0); + + ret &= test_EnumPrinterKey(tctx, b, &handle03, "", ctx); + + for (i=0; ctx->printer_keys && ctx->printer_keys[i] != NULL; i++) { + + ret &= test_EnumPrinterKey(tctx, b, &handle03, + ctx->printer_keys[i], + tmp_ctx); + ret &= test_EnumPrinterDataEx(tctx, b, &handle03, + ctx->printer_keys[i], 0, + WERR_OK); + } + + ret &= test_EnumPrinterDataEx(tctx, b, &handle03, "", 0, + WERR_INVALID_PARAMETER); + + ret &= test_GetPrinter(tctx, b, &handle03, tmp_ctx, 2, 0); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + + ret &= test_GetPrinter(tctx, b, &handle03, tmp_ctx, 2, 2556); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + + ret &= test_GetPrinter(tctx, b, &handle03, tmp_ctx, 7, 0); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + + ret &= test_ClosePrinter(tctx, b, &handle03); + + ret &= test_OpenPrinterEx(tctx, b, &unused_handle2, + ctx->printer_info[0].info2.printername, 0); + ret &= test_ClosePrinter(tctx, b, &unused_handle2); + + ret &= test_OpenPrinterEx(tctx, b, &handle03, server_name, 0); + ret &= test_GetPrinterData(tctx, b, &handle03, "W3SvcInstalled", + WERR_OK, 0); + ret &= test_ClosePrinter(tctx, b, &handle03); + + ret &= test_ClosePrinter(tctx, b, &unused_handle1); + ret &= test_ClosePrinter(tctx, b, &handle02); + + ret &= test_OpenPrinterEx(tctx, b, &handle02, + ctx->printer_info[0].info2.sharename, 0); + ret &= test_GetPrinter(tctx, b, &handle02, tmp_ctx, 2, 0); + ret &= test_ClosePrinter(tctx, b, &handle02); + +end_testWinXP: + ret &= test_ClosePrinter(tctx, b, &handle01); + + talloc_free(tmp_ctx); + talloc_free(ctx); + return ret; +} + +struct torture_suite *torture_rpc_spoolss_win(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "spoolss.win"); + + struct torture_rpc_tcase *tcase = torture_suite_add_rpc_iface_tcase(suite, + "win", &ndr_table_spoolss); + + torture_rpc_tcase_add_test(tcase, "testWinXP", test_WinXP); + + return suite; +} + diff --git a/source4/torture/rpc/srvsvc.c b/source4/torture/rpc/srvsvc.c new file mode 100644 index 0000000..c777f36 --- /dev/null +++ b/source4/torture/rpc/srvsvc.c @@ -0,0 +1,1206 @@ +/* + Unix SMB/CIFS implementation. + test suite for srvsvc rpc operations + + Copyright (C) Stefan (metze) Metzmacher 2003 + + 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_srvsvc_c.h" +#include "torture/rpc/torture_rpc.h" + +/**************************/ +/* srvsvc_NetCharDev */ +/**************************/ +static bool test_NetCharDevGetInfo(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *devname) +{ + NTSTATUS status; + struct srvsvc_NetCharDevGetInfo r; + union srvsvc_NetCharDevInfo info; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.device_name = devname; + r.out.info = &info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + r.in.level = levels[i]; + torture_comment(tctx, "Testing NetCharDevGetInfo level %u on device '%s'\n", + r.in.level, r.in.device_name); + status = dcerpc_srvsvc_NetCharDevGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetCharDevGetInfo failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetCharDevGetInfo failed"); + } + + return true; +} + +static bool test_NetCharDevControl(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *devname) +{ + NTSTATUS status; + struct srvsvc_NetCharDevControl r; + uint32_t opcodes[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.device_name = devname; + + for (i=0;i<ARRAY_SIZE(opcodes);i++) { + ZERO_STRUCT(r.out); + r.in.opcode = opcodes[i]; + torture_comment(tctx, "Testing NetCharDevControl opcode %u on device '%s'\n", + r.in.opcode, r.in.device_name); + status = dcerpc_srvsvc_NetCharDevControl_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetCharDevControl failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetCharDevControl failed"); + } + + return true; +} + +static bool test_NetCharDevEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetCharDevEnum r; + struct srvsvc_NetCharDevInfoCtr info_ctr; + struct srvsvc_NetCharDevCtr0 c0; + struct srvsvc_NetCharDevCtr0 c1; + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.info_ctr = &info_ctr; + r.out.totalentries = &totalentries; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int j; + + info_ctr.level = levels[i]; + + switch(info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr0 = &c1; + break; + } + + torture_comment(tctx, "Testing NetCharDevEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetCharDevEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetCharDevEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetCharDevEnum failed: %s\n", win_errstr(r.out.result)); + continue; + } + + /* call test_NetCharDevGetInfo and test_NetCharDevControl for each returned share */ + if (info_ctr.level == 1) { + for (j=0;j<r.out.info_ctr->ctr.ctr1->count;j++) { + const char *device; + device = r.out.info_ctr->ctr.ctr1->array[j].device; + if (!test_NetCharDevGetInfo(p, tctx, device)) { + return false; + } + if (!test_NetCharDevControl(p, tctx, device)) { + return false; + } + } + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetCharDevQ */ +/**************************/ +static bool test_NetCharDevQGetInfo(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *devicequeue) +{ + NTSTATUS status; + struct srvsvc_NetCharDevQGetInfo r; + union srvsvc_NetCharDevQInfo info; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.queue_name = devicequeue; + r.in.user = talloc_asprintf(tctx,"Administrator"); + r.out.info = &info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + r.in.level = levels[i]; + torture_comment(tctx, "Testing NetCharDevQGetInfo level %u on devicequeue '%s'\n", + r.in.level, r.in.queue_name); + status = dcerpc_srvsvc_NetCharDevQGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetCharDevQGetInfo failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetCharDevQGetInfo failed"); + } + + return true; +} + +#if 0 +static bool test_NetCharDevQSetInfo(struct dcerpc_pipe *p, TALLOC_CTX *mem_ctx, + const char *devicequeue) +{ + NTSTATUS status; + struct srvsvc_NetCharDevQSetInfo r; + uint32_t parm_error; + uint32_t levels[] = {0, 1}; + int i; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(mem_ctx,"\\\\%s",dcerpc_server_name(p)); + r.in.queue_name = devicequeue; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + ZERO_STRUCT(r.out); + parm_error = 0; + r.in.level = levels[i]; + d_printf("testing NetCharDevQSetInfo level %u on devicequeue '%s'\n", + r.in.level, devicequeue); + switch (r.in.level) { + case 0: + r.in.info.info0 = talloc(mem_ctx, struct srvsvc_NetCharDevQInfo0); + r.in.info.info0->device = r.in.queue_name; + break; + case 1: + r.in.info.info1 = talloc(mem_ctx, struct srvsvc_NetCharDevQInfo1); + r.in.info.info1->device = r.in.queue_name; + r.in.info.info1->priority = 0x000; + r.in.info.info1->devices = r.in.queue_name; + r.in.info.info1->users = 0x000; + r.in.info.info1->num_ahead = 0x000; + break; + default: + break; + } + r.in.parm_error = &parm_error; + status = dcerpc_srvsvc_NetCharDevQSetInfo_r(b, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + d_printf("NetCharDevQSetInfo level %u on devicequeue '%s' failed - %s\n", + r.in.level, r.in.queue_name, nt_errstr(status)); + ret = false; + continue; + } + if (!W_ERROR_IS_OK(r.out.result)) { + d_printf("NetCharDevQSetInfo level %u on devicequeue '%s' failed - %s\n", + r.in.level, r.in.queue_name, win_errstr(r.out.result)); + continue; + } + } + + return ret; +} +#endif + +static bool test_NetCharDevQEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetCharDevQEnum r; + struct srvsvc_NetCharDevQInfoCtr info_ctr; + struct srvsvc_NetCharDevQCtr0 c0; + struct srvsvc_NetCharDevQCtr1 c1; + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.user = talloc_asprintf(tctx,"%s","Administrator"); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + int j; + + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + } + torture_comment(tctx, "Testing NetCharDevQEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetCharDevQEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetCharDevQEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetCharDevQEnum failed: %s\n", win_errstr(r.out.result)); + continue; + } + + /* call test_NetCharDevGetInfo and test_NetCharDevControl for each returned share */ + if (info_ctr.level == 1) { + for (j=0;j<r.out.info_ctr->ctr.ctr1->count;j++) { + const char *device; + device = r.out.info_ctr->ctr.ctr1->array[j].device; + if (!test_NetCharDevQGetInfo(p, tctx, device)) { + return false; + } + } + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetConn */ +/**************************/ +static bool test_NetConnEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetConnEnum r; + struct srvsvc_NetConnInfoCtr info_ctr; + struct srvsvc_NetConnCtr0 c0; + struct srvsvc_NetConnCtr1 c1; + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.path = talloc_asprintf(tctx,"%s","IPC$"); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + } + + torture_comment(tctx, "Testing NetConnEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetConnEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetConnEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetConnEnum failed: %s\n", win_errstr(r.out.result)); + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetFile */ +/**************************/ +static bool test_NetFileEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetFileEnum r; + struct srvsvc_NetFileInfoCtr info_ctr; + struct srvsvc_NetFileCtr2 c2; + struct srvsvc_NetFileCtr3 c3; + uint32_t totalentries = 0; + uint32_t levels[] = {2, 3}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.path = NULL; + r.in.user = NULL; + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)4096; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 3: + ZERO_STRUCT(c3); + info_ctr.ctr.ctr3 = &c3; + break; + } + torture_comment(tctx, "Testing NetFileEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetFileEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetFileEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetFileEnum failed: %s\n", win_errstr(r.out.result)); + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetSess */ +/**************************/ +static bool test_NetSessEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetSessEnum r; + struct srvsvc_NetSessInfoCtr info_ctr; + struct srvsvc_NetSessCtr0 c0; + struct srvsvc_NetSessCtr1 c1; + struct srvsvc_NetSessCtr2 c2; + struct srvsvc_NetSessCtr10 c10; + struct srvsvc_NetSessCtr502 c502; + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1, 2, 10, 502}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.client = NULL; + r.in.user = NULL; + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + info_ctr.level = levels[i]; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 10: + ZERO_STRUCT(c10); + info_ctr.ctr.ctr10 = &c10; + break; + case 502: + ZERO_STRUCT(c502); + info_ctr.ctr.ctr502 = &c502; + break; + } + + torture_comment(tctx, "Testing NetSessEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetSessEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetSessEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetSessEnum failed: %s\n", win_errstr(r.out.result)); + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetShare */ +/**************************/ +static bool test_NetShareCheck(struct dcerpc_pipe *p, struct torture_context *tctx, + const char *device_name) +{ + NTSTATUS status; + struct srvsvc_NetShareCheck r; + enum srvsvc_ShareType type; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.device_name = device_name; + r.out.type = &type; + + torture_comment(tctx, + "Testing NetShareCheck on device '%s'\n", r.in.device_name); + + status = dcerpc_srvsvc_NetShareCheck_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "dcerpc_srvsvc_NetShareCheck failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetShareCheck failed"); + + return true; +} + +static bool test_NetShareGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *sharename, bool admin) +{ + NTSTATUS status; + struct srvsvc_NetShareGetInfo r; + union srvsvc_NetShareInfo info; + struct { + uint32_t level; + WERROR anon_status; + WERROR admin_status; + } levels[] = { + { 0, WERR_OK, WERR_OK }, + { 1, WERR_OK, WERR_OK }, + { 2, WERR_ACCESS_DENIED, WERR_OK }, + { 501, WERR_OK, WERR_OK }, + { 502, WERR_ACCESS_DENIED, WERR_OK }, + { 1005, WERR_OK, WERR_OK }, + }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.share_name = sharename; + r.out.info = &info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + WERROR expected; + + r.in.level = levels[i].level; + expected = levels[i].anon_status; + if (admin) expected = levels[i].admin_status; + + torture_comment(tctx, "Testing NetShareGetInfo level %u on share '%s'\n", + r.in.level, r.in.share_name); + + status = dcerpc_srvsvc_NetShareGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetShareGetInfo failed"); + torture_assert_werr_equal(tctx, r.out.result, expected, "NetShareGetInfo failed"); + + if (r.in.level != 2) continue; + if (!r.out.info->info2 || !r.out.info->info2->path) continue; + if (!test_NetShareCheck(p, tctx, r.out.info->info2->path)) { + return false; + } + } + + return true; +} + +static bool test_NetShareGetInfoAdminFull(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareGetInfo(tctx, p, "IPC$", true); +} + +static bool test_NetShareGetInfoAdminAnon(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareGetInfo(tctx, p, "IPC$", false); +} + +static bool test_NetShareAddSetDel(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetShareAdd a; + struct srvsvc_NetShareSetInfo r; + struct srvsvc_NetShareGetInfo q; + struct srvsvc_NetShareDel d; + struct sec_desc_buf sd_buf; + union srvsvc_NetShareInfo info; + struct { + uint32_t level; + WERROR expected; + } levels[] = { + { 0, WERR_INVALID_LEVEL }, + { 1, WERR_OK }, + { 2, WERR_OK }, + { 501, WERR_INVALID_LEVEL }, + { 502, WERR_OK }, + { 1004, WERR_OK }, + { 1005, WERR_OK }, + { 1006, WERR_OK }, +/* { 1007, WERR_OK }, */ + { 1501, WERR_OK }, + }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + a.in.server_unc = r.in.server_unc = q.in.server_unc = d.in.server_unc = + talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.share_name = talloc_strdup(tctx, "testshare"); + + info.info2 = talloc(tctx, struct srvsvc_NetShareInfo2); + info.info2->name = r.in.share_name; + info.info2->type = STYPE_DISKTREE; + info.info2->comment = talloc_strdup(tctx, "test comment"); + info.info2->permissions = 123434566; + info.info2->max_users = -1; + info.info2->current_users = 0; + info.info2->path = talloc_strdup(tctx, "C:\\"); + info.info2->password = NULL; + + a.in.info = &info; + a.in.level = 2; + a.in.parm_error = NULL; + + status = dcerpc_srvsvc_NetShareAdd_r(b, tctx, &a); + torture_assert_ntstatus_ok(tctx, status, "NetShareAdd level 2 on share 'testshare' failed"); + torture_assert_werr_ok(tctx, a.out.result, "NetShareAdd level 2 on share 'testshare' failed"); + + r.in.parm_error = NULL; + + q.in.level = 502; + + for (i = 0; i < ARRAY_SIZE(levels); i++) { + + r.in.level = levels[i].level; + ZERO_STRUCT(r.out); + + torture_comment(tctx, "Testing NetShareSetInfo level %u on share '%s'\n", + r.in.level, r.in.share_name); + + switch (levels[i].level) { + case 0: + info.info0 = talloc(tctx, struct srvsvc_NetShareInfo0); + info.info0->name = r.in.share_name; + break; + case 1: + info.info1 = talloc(tctx, struct srvsvc_NetShareInfo1); + info.info1->name = r.in.share_name; + info.info1->type = STYPE_DISKTREE; + info.info1->comment = talloc_strdup(tctx, "test comment 1"); + break; + case 2: + info.info2 = talloc(tctx, struct srvsvc_NetShareInfo2); + info.info2->name = r.in.share_name; + info.info2->type = STYPE_DISKTREE; + info.info2->comment = talloc_strdup(tctx, "test comment 2"); + info.info2->permissions = 0; + info.info2->max_users = 2; + info.info2->current_users = 1; + info.info2->path = talloc_strdup(tctx, "::BLaH::"); /* "C:\\"); */ + info.info2->password = NULL; + break; + case 501: + info.info501 = talloc(tctx, struct srvsvc_NetShareInfo501); + info.info501->name = r.in.share_name; + info.info501->type = STYPE_DISKTREE; + info.info501->comment = talloc_strdup(tctx, "test comment 501"); + info.info501->csc_policy = 0; + break; + case 502: + ZERO_STRUCT(sd_buf); + info.info502 = talloc(tctx, struct srvsvc_NetShareInfo502); + info.info502->name = r.in.share_name; + info.info502->type = STYPE_DISKTREE; + info.info502->comment = talloc_strdup(tctx, "test comment 502"); + info.info502->permissions = 0; + info.info502->max_users = 502; + info.info502->current_users = 1; + info.info502->path = talloc_strdup(tctx, "C:\\"); + info.info502->password = NULL; + info.info502->sd_buf = sd_buf; + break; + case 1004: + info.info1004 = talloc(tctx, struct srvsvc_NetShareInfo1004); + info.info1004->comment = talloc_strdup(tctx, "test comment 1004"); + break; + case 1005: + info.info1005 = talloc(tctx, struct srvsvc_NetShareInfo1005); + info.info1005->dfs_flags = 0; + break; + case 1006: + info.info1006 = talloc(tctx, struct srvsvc_NetShareInfo1006); + info.info1006->max_users = 1006; + break; +/* case 1007: + info.info1007 = talloc(tctx, struct srvsvc_NetShareInfo1007); + info.info1007->flags = 0; + info.info1007->alternate_directory_name = talloc_strdup(tctx, "test"); + break; +*/ + case 1501: + info.info1501 = talloc_zero(tctx, struct sec_desc_buf); + break; + } + + r.in.info = &info; + + status = dcerpc_srvsvc_NetShareSetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetShareGetInfo failed"); + torture_assert_werr_equal(tctx, r.out.result, levels[i].expected, "NetShareSetInfo failed"); + + q.in.share_name = r.in.share_name; + q.out.info = &info; + + status = dcerpc_srvsvc_NetShareGetInfo_r(b, tctx, &q); + torture_assert_ntstatus_ok(tctx, status, "NetShareGetInfo failed"); + torture_assert_werr_ok(tctx, q.out.result, "NetShareGetInfo failed"); + + torture_assert_str_equal(tctx, q.out.info->info502->name, r.in.share_name, + "share name invalid"); + + switch (levels[i].level) { + case 0: + break; + case 1: + torture_assert_str_equal(tctx, q.out.info->info502->comment, "test comment 1", "comment"); + break; + case 2: + torture_assert_str_equal(tctx, q.out.info->info2->comment, "test comment 2", "comment"); + torture_assert_int_equal(tctx, q.out.info->info2->max_users, 2, "max users"); + torture_assert_str_equal(tctx, q.out.info->info2->path, "C:\\", "path"); + break; + case 501: + torture_assert_str_equal(tctx, q.out.info->info501->comment, "test comment 501", "comment"); + break; + case 502: + torture_assert_str_equal(tctx, q.out.info->info502->comment, "test comment 502", "comment"); + torture_assert_int_equal(tctx, q.out.info->info502->max_users, 502, "max users"); + torture_assert_str_equal(tctx, q.out.info->info502->path, "C:\\", "path"); + break; + case 1004: + torture_assert_str_equal(tctx, q.out.info->info1004->comment, "test comment 1004", + "comment"); + break; + case 1005: + break; + case 1006: + torture_assert_int_equal(tctx, q.out.info->info1006->max_users, 1006, "Max users"); + break; +/* case 1007: + break; +*/ + case 1501: + break; + } + } + + d.in.share_name = r.in.share_name; + d.in.reserved = 0; + + status = dcerpc_srvsvc_NetShareDel_r(b, tctx, &d); + torture_assert_ntstatus_ok(tctx, status, "NetShareDel on share 'testshare502' failed"); + torture_assert_werr_ok(tctx, a.out.result, "NetShareDel on share 'testshare502' failed"); + + return true; +} + +/**************************/ +/* srvsvc_NetShare */ +/**************************/ +static bool test_NetShareEnumAll(struct torture_context *tctx, + struct dcerpc_pipe *p, + bool admin) +{ + NTSTATUS status; + struct srvsvc_NetShareEnumAll r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr0 c0; + struct srvsvc_NetShareCtr1 c1; + struct srvsvc_NetShareCtr2 c2; + struct srvsvc_NetShareCtr501 c501; + struct srvsvc_NetShareCtr502 c502; + uint32_t totalentries = 0; + struct { + uint32_t level; + WERROR anon_status; + WERROR admin_status; + } levels[] = { + { 0, WERR_OK, WERR_OK }, + { 1, WERR_OK, WERR_OK }, + { 2, WERR_ACCESS_DENIED, WERR_OK }, + { 501, WERR_ACCESS_DENIED, WERR_OK }, + { 502, WERR_ACCESS_DENIED, WERR_OK }, + }; + int i; + uint32_t resume_handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info_ctr); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + r.out.resume_handle = &resume_handle; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + + int j; + WERROR expected; + + info_ctr.level = levels[i].level; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 501: + ZERO_STRUCT(c501); + info_ctr.ctr.ctr501 = &c501; + break; + case 502: + ZERO_STRUCT(c502); + info_ctr.ctr.ctr502 = &c502; + break; + } + + expected = levels[i].anon_status; + if (admin) expected = levels[i].admin_status; + + resume_handle = 0; + + torture_comment(tctx, "Testing NetShareEnumAll level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetShareEnumAll_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetShareEnumAll failed"); + torture_assert_werr_equal(tctx, r.out.result, expected, "NetShareEnumAll failed"); + + /* call srvsvc_NetShareGetInfo for each returned share */ + if (info_ctr.level == 2 && r.out.info_ctr->ctr.ctr2) { + for (j=0;j<r.out.info_ctr->ctr.ctr2->count;j++) { + const char *name; + name = r.out.info_ctr->ctr.ctr2->array[j].name; + if (!test_NetShareGetInfo(tctx, p, name, admin)) { + return false; + } + } + } + } + + return true; +} + +static bool test_NetShareEnumAllFull(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareEnumAll(tctx, p, true); +} + +static bool test_NetShareEnumAllAnon(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareEnumAll(tctx, p, false); +} + +static bool test_NetShareEnum(struct torture_context *tctx, + struct dcerpc_pipe *p, bool admin) +{ + NTSTATUS status; + struct srvsvc_NetShareEnum r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr0 c0; + struct srvsvc_NetShareCtr1 c1; + struct srvsvc_NetShareCtr2 c2; + struct srvsvc_NetShareCtr501 c501; + struct srvsvc_NetShareCtr502 c502; + uint32_t totalentries = 0; + struct { + uint32_t level; + WERROR anon_status; + WERROR admin_status; + } levels[] = { + { 0, WERR_OK, WERR_OK }, + { 1, WERR_OK, WERR_OK }, + { 2, WERR_ACCESS_DENIED, WERR_OK }, + { 501, WERR_INVALID_LEVEL, WERR_INVALID_LEVEL }, + { 502, WERR_ACCESS_DENIED, WERR_OK }, + }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.in.info_ctr = &info_ctr; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + WERROR expected; + + info_ctr.level = levels[i].level; + + switch (info_ctr.level) { + case 0: + ZERO_STRUCT(c0); + info_ctr.ctr.ctr0 = &c0; + break; + case 1: + ZERO_STRUCT(c1); + info_ctr.ctr.ctr1 = &c1; + break; + case 2: + ZERO_STRUCT(c2); + info_ctr.ctr.ctr2 = &c2; + break; + case 501: + ZERO_STRUCT(c501); + info_ctr.ctr.ctr501 = &c501; + break; + case 502: + ZERO_STRUCT(c502); + info_ctr.ctr.ctr502 = &c502; + break; + } + + expected = levels[i].anon_status; + if (admin) expected = levels[i].admin_status; + + torture_comment(tctx, "Testing NetShareEnum level %u\n", info_ctr.level); + status = dcerpc_srvsvc_NetShareEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetShareEnum failed"); + torture_assert_werr_equal(tctx, r.out.result, expected, "NetShareEnum failed"); + } + + return true; +} + +static bool test_NetShareEnumFull(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareEnum(tctx, p, true); +} + +static bool test_NetShareEnumAnon(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + return test_NetShareEnum(tctx, p, false); +} + +/**************************/ +/* srvsvc_NetSrv */ +/**************************/ +static bool test_NetSrvGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetSrvGetInfo r; + union srvsvc_NetSrvInfo info; + uint32_t levels[] = {100, 101, 102, 502, 503}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + + for (i=0;i<ARRAY_SIZE(levels);i++) { + r.in.level = levels[i]; + r.out.info = &info; + torture_comment(tctx, "Testing NetSrvGetInfo level %u\n", r.in.level); + status = dcerpc_srvsvc_NetSrvGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetSrvGetInfo failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "NetSrvGetInfo failed: %s\n", win_errstr(r.out.result)); + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetDisk */ +/**************************/ +static bool test_NetDiskEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetDiskEnum r; + struct srvsvc_NetDiskInfo info; + uint32_t totalentries = 0; + uint32_t levels[] = {0}; + int i; + uint32_t resume_handle=0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info); + ZERO_STRUCT(r); + + r.in.server_unc = NULL; + r.in.resume_handle = &resume_handle; + r.in.info = &info; + r.out.info = &info; + r.out.totalentries = &totalentries; + r.out.resume_handle = &resume_handle; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + ZERO_STRUCTP(r.out.info); + r.in.level = levels[i]; + torture_comment(tctx, "Testing NetDiskEnum level %u\n", r.in.level); + status = dcerpc_srvsvc_NetDiskEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetDiskEnum failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetDiskEnum failed"); + } + + return true; +} + +/**************************/ +/* srvsvc_NetTransport */ +/**************************/ +static bool test_NetTransportEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetTransportEnum r; + struct srvsvc_NetTransportInfoCtr transports; + struct srvsvc_NetTransportCtr0 ctr0; + struct srvsvc_NetTransportCtr1 ctr1; + + uint32_t totalentries = 0; + uint32_t levels[] = {0, 1}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(transports); + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s", dcerpc_server_name(p)); + r.in.transports = &transports; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = NULL; + r.out.totalentries = &totalentries; + r.out.transports = &transports; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + transports.level = levels[i]; + switch (transports.level) { + case 0: + ZERO_STRUCT(ctr0); + transports.ctr.ctr0 = &ctr0; + break; + case 1: + ZERO_STRUCT(ctr1); + transports.ctr.ctr1 = &ctr1; + break; + } + torture_comment(tctx, "Testing NetTransportEnum level %u\n", transports.level); + status = dcerpc_srvsvc_NetTransportEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetTransportEnum failed"); + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, "unexpected result: %s\n", win_errstr(r.out.result)); + } + } + + return true; +} + +/**************************/ +/* srvsvc_NetRemoteTOD */ +/**************************/ +static bool test_NetRemoteTOD(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetRemoteTOD r; + struct srvsvc_NetRemoteTODInfo *info = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx,"\\\\%s",dcerpc_server_name(p)); + r.out.info = &info; + + torture_comment(tctx, "Testing NetRemoteTOD\n"); + status = dcerpc_srvsvc_NetRemoteTOD_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetRemoteTOD failed"); + torture_assert_werr_ok(tctx, r.out.result, "NetRemoteTOD failed"); + + return true; +} + +/**************************/ +/* srvsvc_NetName */ +/**************************/ + +static bool test_NetNameValidate(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct srvsvc_NetNameValidate r; + char *invalidc; + char *name; + int i, n, min, max; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_unc = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.flags = 0x0; + + d_printf("Testing NetNameValidate\n"); + + /* valid path types only between 1 and 13 */ + for (i = 1; i < 14; i++) { + +again: + /* let's limit ourselves to a maximum of 4096 bytes */ + r.in.name = name = talloc_array(tctx, char, 4097); + max = 4096; + min = 0; + n = max; + + while (1) { + + /* Find maximum length accepted by this type */ + ZERO_STRUCT(r.out); + r.in.name_type = i; + memset(name, 'A', n); + name[n] = '\0'; + + status = dcerpc_srvsvc_NetNameValidate_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + d_printf("NetNameValidate failed while checking maximum size (%s)\n", + nt_errstr(status)); + break; + } + + if (W_ERROR_IS_OK(r.out.result)) { + min = n; + n += (max - min + 1)/2; + if (n == min) { + /* + * We did not move, so + * do not loop forever + */ + break; + } + continue; + + } else { + if ((min + 1) >= max) break; /* found it */ + + max = n; + n -= (max - min)/2; + continue; + } + } + + talloc_free(name); + + d_printf("Maximum length for type %2d, flags %08x: %d\n", i, r.in.flags, max); + + /* find invalid chars for this type check only ASCII between 0x20 and 0x7e */ + + invalidc = talloc_strdup(tctx, ""); + + for (n = 0x20; n < 0x7e; n++) { + r.in.name = name = talloc_asprintf(tctx, "%c", (char)n); + + status = dcerpc_srvsvc_NetNameValidate_r(b, tctx, &r); + if (!NT_STATUS_IS_OK(status)) { + d_printf("NetNameValidate failed while checking valid chars (%s)\n", + nt_errstr(status)); + break; + } + + if (!W_ERROR_IS_OK(r.out.result)) { + invalidc = talloc_asprintf_append_buffer(invalidc, "%c", (char)n); + } + + talloc_free(name); + } + + d_printf(" Invalid chars for type %2d, flags %08x: \"%s\"\n", i, r.in.flags, invalidc); + + /* only two values are accepted for flags: 0x0 and 0x80000000 */ + if (r.in.flags == 0x0) { + r.in.flags = 0x80000000; + goto again; + } + + r.in.flags = 0x0; + } + + return true; +} + +struct torture_suite *torture_rpc_srvsvc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "srvsvc"); + struct torture_rpc_tcase *tcase; + struct torture_test *test; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "srvsvc (admin access)", &ndr_table_srvsvc); + + torture_rpc_tcase_add_test(tcase, "NetCharDevEnum", test_NetCharDevEnum); + torture_rpc_tcase_add_test(tcase, "NetCharDevQEnum", test_NetCharDevQEnum); + torture_rpc_tcase_add_test(tcase, "NetConnEnum", test_NetConnEnum); + torture_rpc_tcase_add_test(tcase, "NetFileEnum", test_NetFileEnum); + torture_rpc_tcase_add_test(tcase, "NetSessEnum", test_NetSessEnum); + torture_rpc_tcase_add_test(tcase, "NetShareEnumAll", test_NetShareEnumAllFull); + torture_rpc_tcase_add_test(tcase, "NetSrvGetInfo", test_NetSrvGetInfo); + torture_rpc_tcase_add_test(tcase, "NetDiskEnum", test_NetDiskEnum); + torture_rpc_tcase_add_test(tcase, "NetTransportEnum", test_NetTransportEnum); + torture_rpc_tcase_add_test(tcase, "NetRemoteTOD", test_NetRemoteTOD); + torture_rpc_tcase_add_test(tcase, "NetShareEnum", test_NetShareEnumFull); + torture_rpc_tcase_add_test(tcase, "NetShareGetInfo", test_NetShareGetInfoAdminFull); + test = torture_rpc_tcase_add_test(tcase, "NetShareAddSetDel", + test_NetShareAddSetDel); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "NetNameValidate", test_NetNameValidate); + + tcase = torture_suite_add_anon_rpc_iface_tcase(suite, + "srvsvc anonymous access", + &ndr_table_srvsvc); + + torture_rpc_tcase_add_test(tcase, "NetShareEnumAll", + test_NetShareEnumAllAnon); + torture_rpc_tcase_add_test(tcase, "NetShareEnum", + test_NetShareEnumAnon); + torture_rpc_tcase_add_test(tcase, "NetShareGetInfo", + test_NetShareGetInfoAdminAnon); + + return suite; +} diff --git a/source4/torture/rpc/svcctl.c b/source4/torture/rpc/svcctl.c new file mode 100644 index 0000000..746b399 --- /dev/null +++ b/source4/torture/rpc/svcctl.c @@ -0,0 +1,736 @@ +/* + Unix SMB/CIFS implementation. + test suite for svcctl rpc operations + + Copyright (C) Jelmer Vernooij 2004 + Copyright (C) Guenther Deschner 2008,2009,2020 + + 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_svcctl_c.h" +#include "librpc/gen_ndr/ndr_svcctl.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" + +#define TORTURE_DEFAULT_SERVICE "Spooler" + +static bool test_OpenSCManager(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *h) +{ + struct svcctl_OpenSCManagerW r; + + r.in.MachineName = NULL; + r.in.DatabaseName = NULL; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = h; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_OpenSCManagerW_r(b, tctx, &r), + "OpenSCManager failed!"); + + return true; +} + +static bool test_CloseServiceHandle(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *h) +{ + struct svcctl_CloseServiceHandle r; + + r.in.handle = h; + r.out.handle = h; + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_CloseServiceHandle_r(b, tctx, &r), + "CloseServiceHandle failed"); + + return true; +} + +static bool test_OpenService(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *h, + const char *name, + struct policy_handle *s) +{ + struct svcctl_OpenServiceW r; + + r.in.scmanager_handle = h; + r.in.ServiceName = name; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = s; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_OpenServiceW_r(b, tctx, &r), + "OpenServiceW failed!"); + torture_assert_werr_ok(tctx, r.out.result, "OpenServiceW failed!"); + + return true; + +} + +static bool test_QueryServiceStatus(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceStatus r; + struct policy_handle h, s; + struct SERVICE_STATUS service_status; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.handle = &s; + r.out.service_status = &service_status; + + status = dcerpc_svcctl_QueryServiceStatus_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceStatus failed!"); + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceStatus failed!"); + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_QueryServiceStatusEx(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceStatusEx r; + struct policy_handle h, s; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t info_level = SVC_STATUS_PROCESS_INFO; + uint8_t *buffer; + uint32_t offered = 0; + uint32_t needed = 0; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + buffer = talloc(tctx, uint8_t); + + r.in.handle = &s; + r.in.info_level = info_level; + r.in.offered = offered; + r.out.buffer = buffer; + r.out.needed = &needed; + + status = dcerpc_svcctl_QueryServiceStatusEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceStatusEx failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + r.in.offered = needed; + buffer = talloc_array(tctx, uint8_t, needed); + r.out.buffer = buffer; + + status = dcerpc_svcctl_QueryServiceStatusEx_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceStatusEx failed!"); + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceStatusEx failed!"); + } + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_QueryServiceConfigW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceConfigW r; + struct QUERY_SERVICE_CONFIG query; + struct policy_handle h, s; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t offered = 0; + uint32_t needed = 0; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.handle = &s; + r.in.offered = offered; + r.out.query = &query; + r.out.needed = &needed; + + status = dcerpc_svcctl_QueryServiceConfigW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfigW failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + r.in.offered = needed; + status = dcerpc_svcctl_QueryServiceConfigW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfigW failed!"); + } + + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceConfigW failed!"); + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_QueryServiceConfig2W(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceConfig2W r; + struct policy_handle h, s; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint32_t info_level = SERVICE_CONFIG_DESCRIPTION; + uint8_t *buffer; + uint32_t offered = 0; + uint32_t needed = 0; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + buffer = talloc(tctx, uint8_t); + + r.in.handle = &s; + r.in.info_level = info_level; + r.in.offered = offered; + r.out.buffer = buffer; + r.out.needed = &needed; + + status = dcerpc_svcctl_QueryServiceConfig2W_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfig2W failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + r.in.offered = needed; + buffer = talloc_array(tctx, uint8_t, needed); + r.out.buffer = buffer; + + status = dcerpc_svcctl_QueryServiceConfig2W_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfig2W failed!"); + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceConfig2W failed!"); + } + + r.in.info_level = SERVICE_CONFIG_FAILURE_ACTIONS; + r.in.offered = offered; + r.out.buffer = buffer; + r.out.needed = &needed; + + status = dcerpc_svcctl_QueryServiceConfig2W_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfig2W failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + r.in.offered = needed; + buffer = talloc_array(tctx, uint8_t, needed); + r.out.buffer = buffer; + + status = dcerpc_svcctl_QueryServiceConfig2W_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfig2W failed!"); + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceConfig2W failed!"); + } + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_QueryServiceObjectSecurity(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceObjectSecurity r; + struct policy_handle h, s; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint8_t *buffer = NULL; + uint32_t needed; + + enum ndr_err_code ndr_err; + struct security_descriptor sd; + DATA_BLOB blob; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.handle = &s; + r.in.security_flags = 0; + r.in.offered = 0; + r.out.buffer = NULL; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_QueryServiceObjectSecurity_r(b, tctx, &r), + "QueryServiceObjectSecurity failed!"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "QueryServiceObjectSecurity failed!"); + + r.in.security_flags = SECINFO_DACL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_QueryServiceObjectSecurity_r(b, tctx, &r), + "QueryServiceObjectSecurity failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_INSUFFICIENT_BUFFER)) { + r.in.offered = needed; + buffer = talloc_array(tctx, uint8_t, needed); + r.out.buffer = buffer; + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_QueryServiceObjectSecurity_r(b, tctx, &r), + "QueryServiceObjectSecurity failed!"); + } + + torture_assert_werr_ok(tctx, r.out.result, "QueryServiceObjectSecurity failed!"); + + blob = data_blob_const(buffer, needed); + + ndr_err = ndr_pull_struct_blob(&blob, tctx, &sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + + if (DEBUGLEVEL >= 1) { + NDR_PRINT_DEBUG(security_descriptor, &sd); + } + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_SetServiceObjectSecurity(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_QueryServiceObjectSecurity q; + struct svcctl_SetServiceObjectSecurity r; + struct policy_handle h, s; + struct dcerpc_binding_handle *b = p->binding_handle; + + uint8_t *buffer; + uint32_t needed; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + q.in.handle = &s; + q.in.security_flags = SECINFO_DACL; + q.in.offered = 0; + q.out.buffer = NULL; + q.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_QueryServiceObjectSecurity_r(b, tctx, &q), + "QueryServiceObjectSecurity failed!"); + + if (W_ERROR_EQUAL(q.out.result, WERR_INSUFFICIENT_BUFFER)) { + q.in.offered = needed; + buffer = talloc_array(tctx, uint8_t, needed); + q.out.buffer = buffer; + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_QueryServiceObjectSecurity_r(b, tctx, &q), + "QueryServiceObjectSecurity failed!"); + } + + torture_assert_werr_ok(tctx, q.out.result, + "QueryServiceObjectSecurity failed!"); + + r.in.handle = &s; + r.in.security_flags = SECINFO_DACL; + r.in.buffer = q.out.buffer; + r.in.offered = *q.out.needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_SetServiceObjectSecurity_r(b, tctx, &r), + "SetServiceObjectSecurity failed!"); + torture_assert_werr_ok(tctx, r.out.result, + "SetServiceObjectSecurity failed!"); + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_StartServiceW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_StartServiceW r; + struct policy_handle h, s; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.handle = &s; + r.in.NumArgs = 0; + r.in.Arguments = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_StartServiceW_r(b, tctx, &r), + "StartServiceW failed!"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_SERVICE_ALREADY_RUNNING, + "StartServiceW failed!"); + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_ControlService(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_ControlService r; + struct policy_handle h, s; + struct SERVICE_STATUS service_status; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.handle = &s; + r.in.control = 0; + r.out.service_status = &service_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_ControlService_r(b, tctx, &r), + "ControlService failed!"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "ControlService failed!"); + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_EnumServicesStatus(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct svcctl_EnumServicesStatusW r; + struct policy_handle h; + int i; + NTSTATUS status; + uint32_t resume_handle = 0; + struct ENUM_SERVICE_STATUSW *service = NULL; + uint32_t needed = 0; + uint32_t services_returned = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + r.in.handle = &h; + r.in.type = SERVICE_TYPE_WIN32; + r.in.state = SERVICE_STATE_ALL; + r.in.offered = 0; + r.in.resume_handle = &resume_handle; + r.out.service = NULL; + r.out.resume_handle = &resume_handle; + r.out.services_returned = &services_returned; + r.out.needed = &needed; + + status = dcerpc_svcctl_EnumServicesStatusW_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "EnumServicesStatus failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.in.offered = needed; + r.out.service = talloc_array(tctx, uint8_t, needed); + + status = dcerpc_svcctl_EnumServicesStatusW_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, "EnumServicesStatus failed!"); + torture_assert_werr_ok(tctx, r.out.result, "EnumServicesStatus failed"); + } + + if (services_returned > 0) { + + enum ndr_err_code ndr_err; + DATA_BLOB blob; + struct ndr_pull *ndr; + + blob.length = r.in.offered; + blob.data = talloc_steal(tctx, r.out.service); + + ndr = ndr_pull_init_blob(&blob, tctx); + + service = talloc_array(tctx, struct ENUM_SERVICE_STATUSW, services_returned); + if (!service) { + return false; + } + + ndr_err = ndr_pull_ENUM_SERVICE_STATUSW_array( + ndr, services_returned, service); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + } + + for(i = 0; i < services_returned; i++) { + + torture_assert(tctx, service[i].service_name, + "Service without name returned!"); + + printf("%-20s \"%s\", Type: %d, State: %d\n", + service[i].service_name, service[i].display_name, + service[i].status.type, service[i].status.state); + } + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_EnumDependentServicesW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_EnumDependentServicesW r; + struct policy_handle h, s; + uint32_t needed; + uint32_t services_returned; + uint32_t i; + uint32_t states[] = { SERVICE_STATE_ACTIVE, + SERVICE_STATE_INACTIVE, + SERVICE_STATE_ALL }; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s)) + return false; + + r.in.service = &s; + r.in.offered = 0; + r.in.state = 0; + r.out.service_status = NULL; + r.out.services_returned = &services_returned; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_EnumDependentServicesW_r(b, tctx, &r), + "EnumDependentServicesW failed!"); + + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "EnumDependentServicesW failed!"); + + for (i=0; i<ARRAY_SIZE(states); i++) { + + r.in.state = states[i]; + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_EnumDependentServicesW_r(b, tctx, &r), + "EnumDependentServicesW failed!"); + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + r.in.offered = needed; + r.out.service_status = talloc_array(tctx, uint8_t, needed); + + torture_assert_ntstatus_ok(tctx, + dcerpc_svcctl_EnumDependentServicesW_r(b, tctx, &r), + "EnumDependentServicesW failed!"); + + } + + torture_assert_werr_ok(tctx, r.out.result, + "EnumDependentServicesW failed"); + } + + if (!test_CloseServiceHandle(b, tctx, &s)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_SCManager(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct policy_handle h; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenSCManager(b, tctx, &h)) + return false; + + if (!test_CloseServiceHandle(b, tctx, &h)) + return false; + + return true; +} + +static bool test_ChangeServiceConfigW(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct svcctl_ChangeServiceConfigW r; + struct svcctl_QueryServiceConfigW q; + struct policy_handle h, s; + NTSTATUS status; + struct dcerpc_binding_handle *b = p->binding_handle; + struct QUERY_SERVICE_CONFIG query; + bool ok; + + uint32_t offered = 0; + uint32_t needed = 0; + + ok = test_OpenSCManager(b, tctx, &h); + if (!ok) { + return false; + } + + ok = test_OpenService(b, tctx, &h, TORTURE_DEFAULT_SERVICE, &s); + if (!ok) { + return false; + } + + q.in.handle = &s; + q.in.offered = offered; + q.out.query = &query; + q.out.needed = &needed; + + status = dcerpc_svcctl_QueryServiceConfigW_r(b, tctx, &q); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfigW failed!"); + + if (W_ERROR_EQUAL(q.out.result, WERR_INSUFFICIENT_BUFFER)) { + q.in.offered = needed; + status = dcerpc_svcctl_QueryServiceConfigW_r(b, tctx, &q); + torture_assert_ntstatus_ok(tctx, status, "QueryServiceConfigW failed!"); + } + torture_assert_werr_ok(tctx, q.out.result, "QueryServiceConfigW failed!"); + + r.in.handle = &s; + r.in.type = query.service_type; + r.in.start_type = query.start_type; + r.in.error_control = query.error_control; + + /* + * according to MS-SCMR 3.1.4.11 NULL params are supposed to leave the + * existing values intact. + */ + + r.in.binary_path = NULL; + r.in.load_order_group = NULL; + r.in.dependencies = NULL; + r.in.dwDependSize = 0; + r.in.service_start_name = NULL; + r.in.password = NULL; + r.in.dwPwSize = 0; + r.in.display_name = NULL; + r.in.tag_id = NULL; + r.out.tag_id = NULL; + + status = dcerpc_svcctl_ChangeServiceConfigW_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "ChangeServiceConfigW failed!"); + torture_assert_werr_ok(tctx, r.out.result, "ChangeServiceConfigW failed!"); + + ok = test_CloseServiceHandle(b, tctx, &s); + if (!ok) { + return false; + } + + ok = test_CloseServiceHandle(b, tctx, &h); + if (!ok) { + return false; + } + + return true; +} + +struct torture_suite *torture_rpc_svcctl(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "svcctl"); + struct torture_rpc_tcase *tcase; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "svcctl", &ndr_table_svcctl); + + torture_rpc_tcase_add_test(tcase, "SCManager", + test_SCManager); + torture_rpc_tcase_add_test(tcase, "EnumServicesStatus", + test_EnumServicesStatus); + torture_rpc_tcase_add_test(tcase, "EnumDependentServicesW", + test_EnumDependentServicesW); + torture_rpc_tcase_add_test(tcase, "QueryServiceStatus", + test_QueryServiceStatus); + torture_rpc_tcase_add_test(tcase, "QueryServiceStatusEx", + test_QueryServiceStatusEx); + torture_rpc_tcase_add_test(tcase, "QueryServiceConfigW", + test_QueryServiceConfigW); + torture_rpc_tcase_add_test(tcase, "QueryServiceConfig2W", + test_QueryServiceConfig2W); + torture_rpc_tcase_add_test(tcase, "QueryServiceObjectSecurity", + test_QueryServiceObjectSecurity); + torture_rpc_tcase_add_test(tcase, "SetServiceObjectSecurity", + test_SetServiceObjectSecurity); + torture_rpc_tcase_add_test(tcase, "StartServiceW", + test_StartServiceW); + torture_rpc_tcase_add_test(tcase, "ControlService", + test_ControlService); + torture_rpc_tcase_add_test(tcase, "ChangeServiceConfigW", + test_ChangeServiceConfigW); + + return suite; +} diff --git a/source4/torture/rpc/testjoin.c b/source4/torture/rpc/testjoin.c new file mode 100644 index 0000000..0a3c96f --- /dev/null +++ b/source4/torture/rpc/testjoin.c @@ -0,0 +1,915 @@ +/* + Unix SMB/CIFS implementation. + + utility code to join/leave a domain + + 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/>. +*/ + +/* + this code is used by other torture modules to join/leave a domain + as either a member, bdc or thru a trust relationship +*/ + +#include "includes.h" +#include "system/time.h" +#include "libnet/libnet.h" +#include "lib/cmdline/cmdline.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "librpc/gen_ndr/ndr_samr_c.h" + +#include "libcli/auth/libcli_auth.h" +#include "torture/rpc/torture_rpc.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "source3/rpc_client/init_samr.h" + +struct test_join { + struct dcerpc_pipe *p; + struct policy_handle user_handle; + struct policy_handle domain_handle; + struct libnet_JoinDomain *libnet_r; + struct dom_sid *dom_sid; + const char *dom_netbios_name; + const char *dom_dns_name; + struct dom_sid *user_sid; + struct GUID user_guid; + const char *netbios_name; +}; + + +static NTSTATUS DeleteUser_byname(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct policy_handle *handle, const char *name) +{ + NTSTATUS status; + struct samr_DeleteUser d; + struct policy_handle user_handle; + uint32_t rid; + struct samr_LookupNames n; + struct samr_Ids rids, types; + struct lsa_String sname; + struct samr_OpenUser r; + + sname.string = name; + + n.in.domain_handle = handle; + n.in.num_names = 1; + n.in.names = &sname; + n.out.rids = &rids; + n.out.types = &types; + + status = dcerpc_samr_LookupNames_r(b, mem_ctx, &n); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (NT_STATUS_IS_OK(n.out.result)) { + rid = n.out.rids->ids[0]; + } else { + return n.out.result; + } + + r.in.domain_handle = handle; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.rid = rid; + r.out.user_handle = &user_handle; + + status = dcerpc_samr_OpenUser_r(b, mem_ctx, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "OpenUser(%s) failed - %s\n", name, nt_errstr(status)); + return status; + } + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "OpenUser(%s) failed - %s\n", name, nt_errstr(r.out.result)); + return r.out.result; + } + + d.in.user_handle = &user_handle; + d.out.user_handle = &user_handle; + status = dcerpc_samr_DeleteUser_r(b, mem_ctx, &d); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(d.out.result)) { + return d.out.result; + } + + return NT_STATUS_OK; +} + +/* + create a test user in the domain + an opaque pointer is returned. Pass it to torture_leave_domain() + when finished +*/ + +struct test_join *torture_create_testuser_max_pwlen(struct torture_context *tctx, + const char *username, + const char *domain, + uint16_t acct_type, + const char **random_password, + int max_pw_len) +{ + NTSTATUS status; + struct samr_Connect c; + struct samr_CreateUser2 r; + struct samr_OpenDomain o; + struct samr_LookupDomain l; + struct dom_sid2 *sid = NULL; + struct samr_GetUserPwInfo pwp; + struct samr_PwInfo info; + struct samr_SetUserInfo s; + union samr_UserInfo u; + struct policy_handle handle; + uint32_t access_granted; + uint32_t rid; + DATA_BLOB session_key; + struct lsa_String name; + + int policy_min_pw_len = 0; + struct test_join *join; + char *random_pw; + const char *dc_binding = torture_setting_string(tctx, "dc_binding", NULL); + struct dcerpc_binding_handle *b = NULL; + join = talloc(NULL, struct test_join); + if (join == NULL) { + return NULL; + } + + ZERO_STRUCTP(join); + + torture_comment(tctx, "Connecting to SAMR\n"); + + if (dc_binding) { + status = dcerpc_pipe_connect(join, + &join->p, + dc_binding, + &ndr_table_samr, + samba_cmdline_get_creds(), + NULL, tctx->lp_ctx); + + } else { + status = torture_rpc_connection(tctx, + &join->p, + &ndr_table_samr); + } + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + b = join->p->binding_handle; + + c.in.system_name = NULL; + c.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + c.out.connect_handle = &handle; + + status = dcerpc_samr_Connect_r(b, join, &c); + if (!NT_STATUS_IS_OK(status)) { + const char *errstr = nt_errstr(status); + torture_comment(tctx, "samr_Connect failed - %s\n", errstr); + return NULL; + } + if (!NT_STATUS_IS_OK(c.out.result)) { + const char *errstr = nt_errstr(c.out.result); + torture_comment(tctx, "samr_Connect failed - %s\n", errstr); + return NULL; + } + + if (domain) { + torture_comment(tctx, "Opening domain %s\n", domain); + + name.string = domain; + l.in.connect_handle = &handle; + l.in.domain_name = &name; + l.out.sid = &sid; + + status = dcerpc_samr_LookupDomain_r(b, join, &l); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(l.out.result)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(l.out.result)); + goto failed; + } + } else { + struct samr_EnumDomains e; + uint32_t resume_handle = 0, num_entries; + struct samr_SamArray *sam; + int i; + + e.in.connect_handle = &handle; + e.in.buf_size = (uint32_t)-1; + e.in.resume_handle = &resume_handle; + e.out.sam = &sam; + e.out.num_entries = &num_entries; + e.out.resume_handle = &resume_handle; + + status = dcerpc_samr_EnumDomains_r(b, join, &e); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "EnumDomains failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(e.out.result)) { + torture_comment(tctx, "EnumDomains failed - %s\n", nt_errstr(e.out.result)); + goto failed; + } + if ((num_entries != 2) || (sam && sam->count != 2)) { + torture_comment(tctx, "unexpected number of domains\n"); + goto failed; + } + for (i=0; i < 2; i++) { + if (!strequal(sam->entries[i].name.string, "builtin")) { + domain = sam->entries[i].name.string; + break; + } + } + if (domain) { + torture_comment(tctx, "Opening domain %s\n", domain); + + name.string = domain; + l.in.connect_handle = &handle; + l.in.domain_name = &name; + l.out.sid = &sid; + + status = dcerpc_samr_LookupDomain_r(b, join, &l); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(l.out.result)) { + torture_comment(tctx, "LookupDomain failed - %s\n", nt_errstr(l.out.result)); + goto failed; + } + } else { + torture_comment(tctx, "cannot proceed without domain name\n"); + goto failed; + } + } + + talloc_steal(join, *l.out.sid); + join->dom_sid = *l.out.sid; + join->dom_netbios_name = talloc_strdup(join, domain); + if (!join->dom_netbios_name) goto failed; + + o.in.connect_handle = &handle; + o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + o.in.sid = *l.out.sid; + o.out.domain_handle = &join->domain_handle; + + status = dcerpc_samr_OpenDomain_r(b, join, &o); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "OpenDomain failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(o.out.result)) { + torture_comment(tctx, "OpenDomain failed - %s\n", nt_errstr(o.out.result)); + goto failed; + } + + torture_comment(tctx, "Creating account %s\n", username); + +again: + name.string = username; + r.in.domain_handle = &join->domain_handle; + r.in.account_name = &name; + r.in.acct_flags = acct_type; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.user_handle = &join->user_handle; + r.out.access_granted = &access_granted; + r.out.rid = &rid; + + status = dcerpc_samr_CreateUser2_r(b, join, &r); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "CreateUser2 failed - %s\n", nt_errstr(status)); + goto failed; + } + + if (NT_STATUS_EQUAL(r.out.result, NT_STATUS_USER_EXISTS)) { + status = DeleteUser_byname(tctx, b, join, &join->domain_handle, name.string); + if (NT_STATUS_IS_OK(status)) { + goto again; + } + } + + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "CreateUser2 failed - %s\n", nt_errstr(r.out.result)); + goto failed; + } + + join->user_sid = dom_sid_add_rid(join, join->dom_sid, rid); + + pwp.in.user_handle = &join->user_handle; + pwp.out.info = &info; + + status = dcerpc_samr_GetUserPwInfo_r(b, join, &pwp); + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(pwp.out.result)) { + policy_min_pw_len = pwp.out.info->min_password_length; + } + + random_pw = generate_random_password(join, MAX(8, policy_min_pw_len), max_pw_len); + + torture_comment(tctx, "Setting account password '%s'\n", random_pw); + + ZERO_STRUCT(u); + s.in.user_handle = &join->user_handle; + s.in.info = &u; + s.in.level = 24; + + u.info24.password_expired = 0; + + status = dcerpc_fetch_session_key(join->p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "SetUserInfo level %u - no session key - %s\n", + s.in.level, nt_errstr(status)); + torture_leave_domain(tctx, join); + goto failed; + } + + status = init_samr_CryptPassword(random_pw, + &session_key, + &u.info24.password); + torture_assert_ntstatus_ok(tctx, + status, + "init_samr_CryptPassword failed"); + + status = dcerpc_samr_SetUserInfo_r(b, join, &s); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "SetUserInfo failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_comment(tctx, "SetUserInfo failed - %s\n", nt_errstr(s.out.result)); + goto failed; + } + + ZERO_STRUCT(u); + s.in.user_handle = &join->user_handle; + s.in.info = &u; + s.in.level = 21; + + u.info21.acct_flags = acct_type | ACB_PWNOEXP; + u.info21.fields_present = SAMR_FIELD_ACCT_FLAGS | SAMR_FIELD_DESCRIPTION | SAMR_FIELD_COMMENT | SAMR_FIELD_FULL_NAME; + + u.info21.comment.string = talloc_asprintf(join, + "Tortured by Samba4: %s", + timestring(join, time(NULL))); + + u.info21.full_name.string = talloc_asprintf(join, + "Torture account for Samba4: %s", + timestring(join, time(NULL))); + + u.info21.description.string = talloc_asprintf(join, + "Samba4 torture account created by host %s: %s", + lpcfg_netbios_name(tctx->lp_ctx), + timestring(join, time(NULL))); + + torture_comment(tctx, "Resetting ACB flags, force pw change time\n"); + + status = dcerpc_samr_SetUserInfo_r(b, join, &s); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "SetUserInfo failed - %s\n", nt_errstr(status)); + goto failed; + } + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_comment(tctx, "SetUserInfo failed - %s\n", nt_errstr(s.out.result)); + goto failed; + } + + if (random_password) { + *random_password = random_pw; + } + + return join; + +failed: + torture_leave_domain(tctx, join); + return NULL; +} + +/* + * Set privileges on an account. + */ + +static void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s) +{ + name->string = s; +} +static void init_lsa_String(struct lsa_String *name, const char *s) +{ + name->string = s; +} + +bool torture_setup_privs(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint32_t num_privs, + const char **privs, + const struct dom_sid *user_sid) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct policy_handle *handle; + int i; + + torture_assert(tctx, + test_lsa_OpenPolicy2(b, tctx, &handle), + "failed to open policy"); + + for (i=0; i < num_privs; i++) { + struct lsa_LookupPrivValue r; + struct lsa_LUID luid; + struct lsa_String name; + + init_lsa_String(&name, privs[i]); + + r.in.handle = handle; + r.in.name = &name; + r.out.luid = &luid; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_LookupPrivValue_r(b, tctx, &r), + "lsa_LookupPrivValue failed"); + if (!NT_STATUS_IS_OK(r.out.result)) { + torture_comment(tctx, "lsa_LookupPrivValue failed for '%s' with %s\n", + privs[i], nt_errstr(r.out.result)); + return false; + } + } + + { + struct lsa_AddAccountRights r; + struct lsa_RightSet rights; + + rights.count = num_privs; + rights.names = talloc_zero_array(tctx, struct lsa_StringLarge, rights.count); + for (i=0; i < rights.count; i++) { + init_lsa_StringLarge(&rights.names[i], privs[i]); + } + + r.in.handle = handle; + r.in.sid = discard_const_p(struct dom_sid, user_sid); + r.in.rights = &rights; + + torture_assert_ntstatus_ok(tctx, + dcerpc_lsa_AddAccountRights_r(b, tctx, &r), + "lsa_AddAccountRights failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, + "lsa_AddAccountRights failed"); + } + + test_lsa_Close(b, tctx, handle); + + return true; +} + +struct test_join *torture_create_testuser(struct torture_context *torture, + const char *username, + const char *domain, + uint16_t acct_type, + const char **random_password) +{ + return torture_create_testuser_max_pwlen(torture, username, domain, acct_type, random_password, 255); +} + +NTSTATUS torture_delete_testuser(struct torture_context *torture, + struct test_join *join, + const char *username) +{ + NTSTATUS status; + + status = DeleteUser_byname(torture, + join->p->binding_handle, + torture, + &join->domain_handle, + username); + + return status; +} + +_PUBLIC_ struct test_join *torture_join_domain(struct torture_context *tctx, + const char *machine_name, + uint32_t acct_flags, + struct cli_credentials **machine_credentials) +{ + NTSTATUS status; + struct libnet_context *libnet_ctx; + struct libnet_JoinDomain *libnet_r; + struct test_join *tj; + struct samr_SetUserInfo s; + union samr_UserInfo u; + const char *binding_str = NULL; + struct dcerpc_binding *binding = NULL; + enum dcerpc_transport_t transport; + + tj = talloc_zero(tctx, struct test_join); + if (!tj) return NULL; + + binding_str = torture_setting_string(tctx, "binding", NULL); + if (binding_str == NULL) { + const char *host = torture_setting_string(tctx, "host", NULL); + binding_str = talloc_asprintf(tj, "ncacn_np:%s", host); + } + status = dcerpc_parse_binding(tj, binding_str, &binding); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("dcerpc_parse_binding(%s) failed - %s\n", + binding_str, nt_errstr(status))); + talloc_free(tj); + return NULL; + } + transport = dcerpc_binding_get_transport(binding); + switch (transport) { + case NCALRPC: + case NCACN_UNIX_STREAM: + break; + default: + dcerpc_binding_set_transport(binding, NCACN_NP); + dcerpc_binding_set_flags(binding, 0, DCERPC_AUTH_OPTIONS); + break; + } + + libnet_r = talloc_zero(tj, struct libnet_JoinDomain); + if (!libnet_r) { + talloc_free(tj); + return NULL; + } + + libnet_ctx = libnet_context_init(tctx->ev, tctx->lp_ctx); + if (!libnet_ctx) { + talloc_free(tj); + return NULL; + } + + tj->libnet_r = libnet_r; + + libnet_ctx->cred = samba_cmdline_get_creds(); + libnet_r->in.binding = dcerpc_binding_string(libnet_r, binding); + if (libnet_r->in.binding == NULL) { + talloc_free(tj); + return NULL; + } + libnet_r->in.level = LIBNET_JOINDOMAIN_SPECIFIED; + libnet_r->in.netbios_name = machine_name; + libnet_r->in.account_name = talloc_asprintf(libnet_r, "%s$", machine_name); + if (!libnet_r->in.account_name) { + talloc_free(tj); + return NULL; + } + + libnet_r->in.acct_type = acct_flags; + libnet_r->in.recreate_account = true; + + status = libnet_JoinDomain(libnet_ctx, libnet_r, libnet_r); + if (!NT_STATUS_IS_OK(status)) { + if (libnet_r->out.error_string) { + DEBUG(0, ("Domain join failed - %s\n", libnet_r->out.error_string)); + } else { + DEBUG(0, ("Domain join failed - %s\n", nt_errstr(status))); + } + talloc_free(tj); + return NULL; + } + tj->p = libnet_r->out.samr_pipe; + tj->user_handle = *libnet_r->out.user_handle; + tj->dom_sid = libnet_r->out.domain_sid; + talloc_steal(tj, libnet_r->out.domain_sid); + tj->dom_netbios_name = libnet_r->out.domain_name; + talloc_steal(tj, libnet_r->out.domain_name); + tj->dom_dns_name = libnet_r->out.realm; + talloc_steal(tj, libnet_r->out.realm); + tj->user_guid = libnet_r->out.account_guid; + tj->netbios_name = talloc_strdup(tj, machine_name); + if (!tj->netbios_name) { + talloc_free(tj); + return NULL; + } + + ZERO_STRUCT(u); + s.in.user_handle = &tj->user_handle; + s.in.info = &u; + s.in.level = 21; + + u.info21.fields_present = SAMR_FIELD_DESCRIPTION | SAMR_FIELD_COMMENT | SAMR_FIELD_FULL_NAME; + u.info21.comment.string = talloc_asprintf(tj, + "Tortured by Samba4: %s", + timestring(tj, time(NULL))); + u.info21.full_name.string = talloc_asprintf(tj, + "Torture account for Samba4: %s", + timestring(tj, time(NULL))); + + u.info21.description.string = talloc_asprintf(tj, + "Samba4 torture account created by host %s: %s", + lpcfg_netbios_name(tctx->lp_ctx), timestring(tj, time(NULL))); + + status = dcerpc_samr_SetUserInfo_r(tj->p->binding_handle, tj, &s); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "SetUserInfo (non-critical) failed - %s\n", nt_errstr(status)); + } + if (!NT_STATUS_IS_OK(s.out.result)) { + torture_comment(tctx, "SetUserInfo (non-critical) failed - %s\n", nt_errstr(s.out.result)); + } + + *machine_credentials = cli_credentials_init(tj); + cli_credentials_set_conf(*machine_credentials, tctx->lp_ctx); + cli_credentials_set_workstation(*machine_credentials, machine_name, CRED_SPECIFIED); + cli_credentials_set_domain(*machine_credentials, libnet_r->out.domain_name, CRED_SPECIFIED); + if (libnet_r->out.realm) { + cli_credentials_set_realm(*machine_credentials, libnet_r->out.realm, CRED_SPECIFIED); + } + cli_credentials_set_username(*machine_credentials, libnet_r->in.account_name, CRED_SPECIFIED); + cli_credentials_set_password(*machine_credentials, libnet_r->out.join_password, CRED_SPECIFIED); + cli_credentials_set_kvno(*machine_credentials, libnet_r->out.kvno); + if (acct_flags & ACB_SVRTRUST) { + cli_credentials_set_secure_channel_type(*machine_credentials, + SEC_CHAN_BDC); + } else if (acct_flags & ACB_WSTRUST) { + cli_credentials_set_secure_channel_type(*machine_credentials, + SEC_CHAN_WKSTA); + } else { + DEBUG(0, ("Invalid account type specificed to torture_join_domain\n")); + talloc_free(*machine_credentials); + return NULL; + } + + return tj; +} + +struct dcerpc_pipe *torture_join_samr_pipe(struct test_join *join) +{ + return join->p; +} + +struct policy_handle *torture_join_samr_user_policy(struct test_join *join) +{ + return &join->user_handle; +} + +static NTSTATUS torture_leave_ads_domain(struct torture_context *torture, + TALLOC_CTX *mem_ctx, + struct libnet_JoinDomain *libnet_r) +{ + int rtn; + TALLOC_CTX *tmp_ctx; + + struct ldb_dn *server_dn; + struct ldb_context *ldb_ctx; + + char *remote_ldb_url; + + /* Check if we are a domain controller. If not, exit. */ + if (!libnet_r->out.server_dn_str) { + return NT_STATUS_OK; + } + + tmp_ctx = talloc_named(mem_ctx, 0, "torture_leave temporary context"); + if (!tmp_ctx) { + libnet_r->out.error_string = NULL; + return NT_STATUS_NO_MEMORY; + } + + ldb_ctx = ldb_init(tmp_ctx, torture->ev); + if (!ldb_ctx) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* Remove CN=Servers,... entry from the AD. */ + server_dn = ldb_dn_new(tmp_ctx, ldb_ctx, libnet_r->out.server_dn_str); + if (! ldb_dn_validate(server_dn)) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + remote_ldb_url = talloc_asprintf(tmp_ctx, "ldap://%s", + dcerpc_binding_get_string_option(libnet_r->out.samr_binding, "host")); + if (!remote_ldb_url) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ldb_set_opaque(ldb_ctx, "credentials", samba_cmdline_get_creds()); + ldb_set_opaque(ldb_ctx, "loadparm", samba_cmdline_get_lp_ctx()); + + rtn = ldb_connect(ldb_ctx, remote_ldb_url, 0, NULL); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + rtn = ldb_delete(ldb_ctx, server_dn); + if (rtn != LDB_SUCCESS) { + libnet_r->out.error_string = NULL; + talloc_free(tmp_ctx); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(0, ("%s removed successfully.\n", libnet_r->out.server_dn_str)); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/* + leave the domain, deleting the machine acct +*/ + +_PUBLIC_ void torture_leave_domain(struct torture_context *tctx, struct test_join *join) +{ + struct samr_DeleteUser d; + NTSTATUS status; + + if (!join) { + return; + } + d.in.user_handle = &join->user_handle; + d.out.user_handle = &join->user_handle; + + /* Delete machine account */ + status = dcerpc_samr_DeleteUser_r(join->p->binding_handle, join, &d); + if (!NT_STATUS_IS_OK(status)) { + torture_comment(tctx, "DeleteUser failed\n"); + } else if (!NT_STATUS_IS_OK(d.out.result)) { + torture_comment(tctx, "Delete of machine account %s failed\n", + join->netbios_name); + } else { + torture_comment(tctx, "Delete of machine account %s was successful.\n", + join->netbios_name); + } + + if (join->libnet_r) { + status = torture_leave_ads_domain(tctx, join, join->libnet_r); + } + + talloc_free(join); +} + +/* + return the dom sid for a test join +*/ +_PUBLIC_ const struct dom_sid *torture_join_sid(struct test_join *join) +{ + return join->dom_sid; +} + +const struct dom_sid *torture_join_user_sid(struct test_join *join) +{ + return join->user_sid; +} + +const char *torture_join_netbios_name(struct test_join *join) +{ + return join->netbios_name; +} + +const struct GUID *torture_join_user_guid(struct test_join *join) +{ + return &join->user_guid; +} + +const char *torture_join_dom_netbios_name(struct test_join *join) +{ + return join->dom_netbios_name; +} + +const char *torture_join_dom_dns_name(struct test_join *join) +{ + return join->dom_dns_name; +} + +#if 0 /* Left as the documentation of the join process, but see new implementation in libnet_become_dc.c */ +struct test_join_ads_dc { + struct test_join *join; +}; + +struct test_join_ads_dc *torture_join_domain_ads_dc(const char *machine_name, + const char *domain, + struct cli_credentials **machine_credentials) +{ + struct test_join_ads_dc *join; + + join = talloc(NULL, struct test_join_ads_dc); + if (join == NULL) { + return NULL; + } + + join->join = torture_join_domain(machine_name, + ACB_SVRTRUST, + machine_credentials); + + if (!join->join) { + return NULL; + } + +/* W2K: */ + /* W2K: modify userAccountControl from 4096 to 532480 */ + + /* W2K: modify RDN to OU=Domain Controllers and skip the $ from server name */ + + /* ask objectVersion of Schema Partition */ + + /* ask rIDManagerReferenz of the Domain Partition */ + + /* ask fsMORoleOwner of the RID-Manager$ object + * returns CN=NTDS Settings,CN=<DC>,CN=Servers,CN=Default-First-Site-Name, ... + */ + + /* ask for dnsHostName of CN=<DC>,CN=Servers,CN=Default-First-Site-Name, ... */ + + /* ask for objectGUID of CN=NTDS Settings,CN=<DC>,CN=Servers,CN=Default-First-Site-Name, ... */ + + /* ask for * of CN=Default-First-Site-Name, ... */ + + /* search (&(|(objectClass=user)(objectClass=computer))(sAMAccountName=<machine_name>$)) in Domain Partition + * attributes : distinguishedName, userAccountControl + */ + + /* ask * for CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name,... + * should fail with noSuchObject + */ + + /* add CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name,... + * + * objectClass = server + * systemFlags = 50000000 + * serverReferenz = CN=<machine_name>,OU=Domain Controllers,... + */ + + /* ask for * of CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name, ... + * should fail with noSuchObject + */ + + /* search for (ncname=<domain_nc>) in CN=Partitions,CN=Configuration,... + * attributes: ncName, dnsRoot + */ + + /* modify add CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name,... + * serverReferenz = CN=<machine_name>,OU=Domain Controllers,... + * should fail with attributeOrValueExists + */ + + /* modify replace CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name,... + * serverReferenz = CN=<machine_name>,OU=Domain Controllers,... + */ + + /* DsAddEntry to create the CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name, ... + * + */ + + /* replicate CN=Schema,CN=Configuration,... + * using DRSUAPI_DS_BIND_GUID_W2K ("6abec3d1-3054-41c8-a362-5a0c5b7d5d71") + * + */ + + /* replicate CN=Configuration,... + * using DRSUAPI_DS_BIND_GUID_W2K ("6abec3d1-3054-41c8-a362-5a0c5b7d5d71") + * + */ + + /* replicate Domain Partition + * using DRSUAPI_DS_BIND_GUID_W2K ("6abec3d1-3054-41c8-a362-5a0c5b7d5d71") + * + */ + + /* call DsReplicaUpdateRefs() for all partitions like this: + * req1: struct drsuapi_DsReplicaUpdateRefsRequest1 + * naming_context : * + * naming_context: struct drsuapi_DsReplicaObjectIdentifier + * __ndr_size : 0x000000ae (174) + * __ndr_size_sid : 0x00000000 (0) + * guid : 00000000-0000-0000-0000-000000000000 + * sid : S-0-0 + * dn : 'CN=Schema,CN=Configuration,DC=w2k3,DC=vmnet1,DC=vm,DC=base' + * dest_dsa_dns_name : * + * dest_dsa_dns_name : '4a0df188-a0b8-47ea-bbe5-e614723f16dd._msdcs.w2k3.vmnet1.vm.base' + * dest_dsa_guid : 4a0df188-a0b8-47ea-bbe5-e614723f16dd + * options : 0x0000001c (28) + * 0: DRSUAPI_DS_REPLICA_UPDATE_ASYNCHRONOUS_OPERATION + * 0: DRSUAPI_DS_REPLICA_UPDATE_WRITEABLE + * 1: DRSUAPI_DS_REPLICA_UPDATE_ADD_REFERENCE + * 1: DRSUAPI_DS_REPLICA_UPDATE_DELETE_REFERENCE + * 1: DRSUAPI_DS_REPLICA_UPDATE_0x00000010 + * + * 4a0df188-a0b8-47ea-bbe5-e614723f16dd is the objectGUID the DsAddEntry() returned for the + * CN=NTDS Settings,CN=<machine_name>,CN=Servers,CN=Default-First-Site-Name, ... + */ + +/* W2K3: see libnet/libnet_become_dc.c */ + return join; +} + +#endif diff --git a/source4/torture/rpc/torture_rpc.h b/source4/torture/rpc/torture_rpc.h new file mode 100644 index 0000000..9217461 --- /dev/null +++ b/source4/torture/rpc/torture_rpc.h @@ -0,0 +1,126 @@ +/* + Unix SMB/CIFS implementation. + SMB torture tester + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Jelmer Vernooij 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/>. +*/ + +#ifndef __TORTURE_RPC_H__ +#define __TORTURE_RPC_H__ + +#include "lib/torture/torture.h" +#include "auth/credentials/credentials.h" +#include "torture/rpc/drsuapi.h" +#include "libnet/libnet_join.h" +#include "librpc/rpc/dcerpc.h" +#include "libcli/raw/libcliraw.h" +#include "librpc/gen_ndr/ndr_spoolss.h" +#include "torture/rpc/proto.h" + +struct torture_rpc_tcase { + struct torture_tcase tcase; + const struct ndr_interface_table *table; + const char *machine_name; + bool (*setup_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *); + bool (*teardown_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *); +}; + +struct torture_rpc_tcase_data { + struct test_join *join_ctx; + struct dcerpc_pipe *pipe; + struct cli_credentials *credentials; +}; + +NTSTATUS torture_rpc_connection(struct torture_context *tctx, + struct dcerpc_pipe **p, + const struct ndr_interface_table *table); +NTSTATUS torture_rpc_connection_with_binding(struct torture_context *tctx, + struct dcerpc_binding *binding, + struct dcerpc_pipe **p, + const struct ndr_interface_table *table); + +struct test_join *torture_join_domain(struct torture_context *tctx, + const char *machine_name, + uint32_t acct_flags, + struct cli_credentials **machine_credentials); +const struct dom_sid *torture_join_sid(struct test_join *join); +void torture_leave_domain(struct torture_context *tctx, struct test_join *join); +struct torture_rpc_tcase *torture_suite_add_rpc_iface_tcase(struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table); +struct torture_rpc_tcase *torture_suite_add_rpc_setup_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + bool (*setup_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *), + bool (*teardown_fn)(struct torture_context *, + struct dcerpc_pipe *, + void *)); + +struct torture_test *torture_rpc_tcase_add_test( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *)); +struct torture_rpc_tcase *torture_suite_add_anon_rpc_iface_tcase(struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table); + +struct torture_test *torture_rpc_tcase_add_test_join( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, + struct cli_credentials *, struct test_join *)); +_PUBLIC_ struct torture_test *torture_rpc_tcase_add_test_setup( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn)(struct torture_context *, + struct dcerpc_pipe *, + void *), + void *userdata); +struct torture_test *torture_rpc_tcase_add_test_ex( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, + void *), + void *userdata); +struct torture_rpc_tcase *torture_suite_add_machine_bdc_rpc_iface_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + const char *machine_name); +struct torture_rpc_tcase *torture_suite_add_machine_workstation_rpc_iface_tcase( + struct torture_suite *suite, + const char *name, + const struct ndr_interface_table *table, + const char *machine_name); +struct torture_test *torture_rpc_tcase_add_test_creds( + struct torture_rpc_tcase *tcase, + const char *name, + bool (*fn) (struct torture_context *, struct dcerpc_pipe *, struct cli_credentials *)); +bool torture_suite_init_rpc_tcase(struct torture_suite *suite, + struct torture_rpc_tcase *tcase, + const char *name, + const struct ndr_interface_table *table); + + + +#endif /* __TORTURE_RPC_H__ */ diff --git a/source4/torture/rpc/unixinfo.c b/source4/torture/rpc/unixinfo.c new file mode 100644 index 0000000..227b002 --- /dev/null +++ b/source4/torture/rpc/unixinfo.c @@ -0,0 +1,149 @@ +/* + Unix SMB/CIFS implementation. + test suite for unixinfo rpc operations + + 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 "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_unixinfo_c.h" +#include "libcli/security/security.h" + +/** + test the SidToUid interface +*/ +static bool test_sidtouid(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct unixinfo_SidToUid r; + struct dom_sid *sid; + uint64_t uid; + struct dcerpc_binding_handle *b = p->binding_handle; + + sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1234-5432"); + r.in.sid = *sid; + r.out.uid = &uid; + + torture_assert_ntstatus_ok(tctx, dcerpc_unixinfo_SidToUid_r(b, tctx, &r), + "SidToUid failed"); + if (NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, r.out.result)) { + } else torture_assert_ntstatus_ok(tctx, r.out.result, "SidToUid failed"); + + return true; +} + +/* + test the UidToSid interface +*/ +static bool test_uidtosid(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct unixinfo_UidToSid r; + struct dom_sid sid; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.uid = 1000; + r.out.sid = &sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_unixinfo_UidToSid_r(b, tctx, &r), + "UidToSid failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "UidToSid failed"); + return true; +} + +static bool test_getpwuid(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + uint64_t uids[512]; + uint32_t num_uids = ARRAY_SIZE(uids); + uint32_t i; + struct unixinfo_GetPWUid r; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i<num_uids; i++) { + uids[i] = i; + } + + r.in.count = &num_uids; + r.in.uids = uids; + r.out.count = &num_uids; + r.out.infos = talloc_array(tctx, struct unixinfo_GetPWUidInfo, num_uids); + + torture_assert_ntstatus_ok(tctx, dcerpc_unixinfo_GetPWUid_r(b, tctx, &r), + "GetPWUid failed"); + + torture_assert_ntstatus_ok(tctx, r.out.result, "GetPWUid failed"); + + return true; +} + +/* + test the SidToGid interface +*/ +static bool test_sidtogid(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct unixinfo_SidToGid r; + struct dom_sid *sid; + uint64_t gid; + struct dcerpc_binding_handle *b = p->binding_handle; + + sid = dom_sid_parse_talloc(tctx, "S-1-5-32-1234-5432"); + r.in.sid = *sid; + r.out.gid = &gid; + + torture_assert_ntstatus_ok(tctx, dcerpc_unixinfo_SidToGid_r(b, tctx, &r), + "SidToGid failed"); + if (NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, r.out.result)) { + } else torture_assert_ntstatus_ok(tctx, r.out.result, "SidToGid failed"); + + return true; +} + +/* + test the GidToSid interface +*/ +static bool test_gidtosid(struct torture_context *tctx, struct dcerpc_pipe *p) +{ + struct unixinfo_GidToSid r; + struct dom_sid sid; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.gid = 1000; + r.out.sid = &sid; + + torture_assert_ntstatus_ok(tctx, dcerpc_unixinfo_GidToSid_r(b, tctx, &r), + "GidToSid failed"); + torture_assert_ntstatus_ok(tctx, r.out.result, "GidToSid failed"); + + return true; +} + +struct torture_suite *torture_rpc_unixinfo(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + + suite = torture_suite_create(mem_ctx, "unixinfo"); + tcase = torture_suite_add_rpc_iface_tcase(suite, "unixinfo", + &ndr_table_unixinfo); + + torture_rpc_tcase_add_test(tcase, "sidtouid", test_sidtouid); + torture_rpc_tcase_add_test(tcase, "uidtosid", test_uidtosid); + torture_rpc_tcase_add_test(tcase, "getpwuid", test_getpwuid); + torture_rpc_tcase_add_test(tcase, "sidtogid", test_sidtogid); + torture_rpc_tcase_add_test(tcase, "gidtosid", test_gidtosid); + + return suite; +} diff --git a/source4/torture/rpc/winreg.c b/source4/torture/rpc/winreg.c new file mode 100644 index 0000000..28e435c --- /dev/null +++ b/source4/torture/rpc/winreg.c @@ -0,0 +1,3245 @@ +/* + Unix SMB/CIFS implementation. + test suite for winreg rpc operations + + Copyright (C) Tim Potter 2003 + Copyright (C) Jelmer Vernooij 2004-2007 + Copyright (C) Günther Deschner 2007,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 "librpc/gen_ndr/ndr_winreg_c.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "torture/rpc/torture_rpc.h" +#include "param/param.h" +#include "lib/registry/registry.h" + +#define TEST_KEY_BASE "winreg_torture_test" +#define TEST_KEY1 "spottyfoot" +#define TEST_KEY2 "with a SD (#1)" +#define TEST_KEY3 "with a subkey" +#define TEST_KEY4 "sd_tests" +#define TEST_SUBKEY "subkey" +#define TEST_SUBKEY_SD "subkey_sd" +#define TEST_SUBSUBKEY_SD "subkey_sd\\subsubkey_sd" +#define TEST_VALUE "torture_value_name" +#define TEST_KEY_VOLATILE "torture_volatile_key" +#define TEST_SUBKEY_VOLATILE "torture_volatile_subkey" +#define TEST_KEY_SYMLINK "torture_symlink_key" +#define TEST_KEY_SYMLINK_DEST "torture_symlink_dest" + +#define TEST_SID "S-1-5-21-1234567890-1234567890-1234567890-500" + +static void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s) +{ + name->string = s; +} + +static void init_winreg_String(struct winreg_String *name, const char *s) +{ + name->name = s; + if (s) { + name->name_len = 2 * (strlen_m(s) + 1); + name->name_size = name->name_len; + } else { + name->name_len = 0; + name->name_size = 0; + } +} + +static bool test_GetVersion(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct winreg_GetVersion r; + uint32_t v; + + torture_comment(tctx, "Testing GetVersion\n"); + + ZERO_STRUCT(r); + r.in.handle = handle; + r.out.version = &v; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_GetVersion_r(b, tctx, &r), + "GetVersion failed"); + + torture_assert_werr_ok(tctx, r.out.result, "GetVersion failed"); + + return true; +} + +static bool test_NotifyChangeKeyValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct winreg_NotifyChangeKeyValue r; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.in.watch_subtree = true; + r.in.notify_filter = 0; + r.in.unknown = r.in.unknown2 = 0; + init_winreg_String(&r.in.string1, NULL); + init_winreg_String(&r.in.string2, NULL); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_NotifyChangeKeyValue_r(b, tctx, &r), + "NotifyChangeKeyValue failed"); + + if (!W_ERROR_IS_OK(r.out.result)) { + torture_comment(tctx, + "NotifyChangeKeyValue failed - %s - not considering\n", + win_errstr(r.out.result)); + return true; + } + + return true; +} + +static bool test_CreateKey_opts(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *name, + const char *kclass, + uint32_t options, + uint32_t access_mask, + struct winreg_SecBuf *secdesc, + WERROR expected_result, + enum winreg_CreateAction *action_taken_p, + struct policy_handle *new_handle_p) +{ + struct winreg_CreateKey r; + struct policy_handle newhandle; + enum winreg_CreateAction action_taken = 0; + + torture_comment(tctx, "Testing CreateKey(%s)\n", name); + + ZERO_STRUCT(r); + r.in.handle = handle; + init_winreg_String(&r.in.name, name); + init_winreg_String(&r.in.keyclass, kclass); + r.in.options = options; + r.in.access_mask = access_mask; + r.in.action_taken = &action_taken; + r.in.secdesc = secdesc; + r.out.new_handle = &newhandle; + r.out.action_taken = &action_taken; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_CreateKey_r(b, tctx, &r), + "CreateKey failed"); + + torture_assert_werr_equal(tctx, r.out.result, expected_result, "CreateKey failed"); + + if (new_handle_p) { + *new_handle_p = newhandle; + } + if (action_taken_p) { + *action_taken_p = *r.out.action_taken; + } + + return true; +} + +static bool test_CreateKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *name, + const char *kclass) +{ + return test_CreateKey_opts(tctx, b, handle, name, kclass, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, /* secdesc */ + WERR_OK, + NULL, /* action_taken */ + NULL /* new_handle */); +} + +/* + createkey testing with a SD +*/ +static bool test_CreateKey_sd(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *name, + const char *kclass, + struct policy_handle *newhandle) +{ + struct winreg_CreateKey r; + enum winreg_CreateAction action_taken = 0; + struct security_descriptor *sd; + DATA_BLOB sdblob; + struct winreg_SecBuf secbuf; + + sd = security_descriptor_dacl_create(tctx, + 0, + NULL, NULL, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_OBJECT_INHERIT | + SEC_ACE_FLAG_CONTAINER_INHERIT, + NULL); + + torture_assert_ndr_success(tctx, + ndr_push_struct_blob(&sdblob, tctx, sd, + (ndr_push_flags_fn_t)ndr_push_security_descriptor), + "Failed to push security_descriptor ?!\n"); + + secbuf.sd.data = sdblob.data; + secbuf.sd.len = sdblob.length; + secbuf.sd.size = sdblob.length; + secbuf.length = sdblob.length-10; + secbuf.inherit = 0; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.out.new_handle = newhandle; + init_winreg_String(&r.in.name, name); + init_winreg_String(&r.in.keyclass, kclass); + r.in.options = 0x0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.in.action_taken = r.out.action_taken = &action_taken; + r.in.secdesc = &secbuf; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_CreateKey_r(b, tctx, &r), + "CreateKey with sd failed"); + + torture_assert_werr_ok(tctx, r.out.result, "CreateKey with sd failed"); + + return true; +} + +static bool _test_GetKeySecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t *sec_info_ptr, + WERROR get_werr, + struct security_descriptor **sd_out) +{ + struct winreg_GetKeySecurity r; + struct security_descriptor *sd = NULL; + uint32_t sec_info; + DATA_BLOB sdblob; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (sec_info_ptr) { + sec_info = *sec_info_ptr; + } else { + sec_info = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL; + } + + ZERO_STRUCT(r); + + r.in.handle = handle; + r.in.sec_info = sec_info; + r.in.sd = r.out.sd = talloc_zero(tctx, struct KeySecurityData); + r.in.sd->size = 0x1000; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_GetKeySecurity_r(b, tctx, &r), + "GetKeySecurity failed"); + + torture_assert_werr_equal(tctx, r.out.result, get_werr, + "GetKeySecurity failed"); + + sdblob.data = r.out.sd->data; + sdblob.length = r.out.sd->len; + + sd = talloc_zero(tctx, struct security_descriptor); + + torture_assert_ndr_success(tctx, + ndr_pull_struct_blob(&sdblob, tctx, sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor), + "pull_security_descriptor failed"); + + if (p->conn->flags & DCERPC_DEBUG_PRINT_OUT) { + NDR_PRINT_DEBUG(security_descriptor, sd); + } + + if (sd_out) { + *sd_out = sd; + } else { + talloc_free(sd); + } + + return true; +} + +static bool test_GetKeySecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + struct security_descriptor **sd_out) +{ + return _test_GetKeySecurity(p, tctx, handle, NULL, WERR_OK, sd_out); +} + +static bool _test_SetKeySecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t *sec_info_ptr, + struct security_descriptor *sd, + WERROR werr) +{ + struct winreg_SetKeySecurity r; + struct KeySecurityData *sdata = NULL; + DATA_BLOB sdblob; + uint32_t sec_info; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + + if (sd && (p->conn->flags & DCERPC_DEBUG_PRINT_OUT)) { + NDR_PRINT_DEBUG(security_descriptor, sd); + } + + torture_assert_ndr_success(tctx, + ndr_push_struct_blob(&sdblob, tctx, sd, + (ndr_push_flags_fn_t)ndr_push_security_descriptor), + "push_security_descriptor failed"); + + sdata = talloc_zero(tctx, struct KeySecurityData); + sdata->data = sdblob.data; + sdata->size = sdblob.length; + sdata->len = sdblob.length; + + if (sec_info_ptr) { + sec_info = *sec_info_ptr; + } else { + sec_info = SECINFO_UNPROTECTED_SACL | + SECINFO_UNPROTECTED_DACL; + if (sd->owner_sid) { + sec_info |= SECINFO_OWNER; + } + if (sd->group_sid) { + sec_info |= SECINFO_GROUP; + } + if (sd->sacl) { + sec_info |= SECINFO_SACL; + } + if (sd->dacl) { + sec_info |= SECINFO_DACL; + } + } + + r.in.handle = handle; + r.in.sec_info = sec_info; + r.in.sd = sdata; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_SetKeySecurity_r(b, tctx, &r), + "SetKeySecurity failed"); + + torture_assert_werr_equal(tctx, r.out.result, werr, + "SetKeySecurity failed"); + + return true; +} + +static bool test_SetKeySecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + struct security_descriptor *sd) +{ + return _test_SetKeySecurity(p, tctx, handle, NULL, sd, WERR_OK); +} + +static bool test_CloseKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct winreg_CloseKey r; + + ZERO_STRUCT(r); + r.in.handle = r.out.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_CloseKey_r(b, tctx, &r), + "CloseKey failed"); + + torture_assert_werr_ok(tctx, r.out.result, "CloseKey failed"); + + return true; +} + +static bool test_FlushKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + struct winreg_FlushKey r; + + ZERO_STRUCT(r); + r.in.handle = handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_FlushKey_r(b, tctx, &r), + "FlushKey failed"); + + torture_assert_werr_ok(tctx, r.out.result, "FlushKey failed"); + + return true; +} + +static bool test_OpenKey_opts(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *hive_handle, + const char *keyname, + uint32_t options, + uint32_t access_mask, + struct policy_handle *key_handle, + WERROR expected_result) +{ + struct winreg_OpenKey r; + + ZERO_STRUCT(r); + r.in.parent_handle = hive_handle; + init_winreg_String(&r.in.keyname, keyname); + r.in.options = options; + r.in.access_mask = access_mask; + r.out.handle = key_handle; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_OpenKey_r(b, tctx, &r), + "OpenKey failed"); + + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "OpenKey failed"); + + return true; +} + +static bool test_OpenKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *hive_handle, + const char *keyname, struct policy_handle *key_handle) +{ + return test_OpenKey_opts(tctx, b, hive_handle, keyname, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + key_handle, + WERR_OK); +} + +static bool test_Cleanup(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *key) +{ + struct winreg_DeleteKey r; + + ZERO_STRUCT(r); + r.in.handle = handle; + + init_winreg_String(&r.in.key, key); + dcerpc_winreg_DeleteKey_r(b, tctx, &r); + + return true; +} + +static bool _test_GetSetSecurityDescriptor(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + WERROR get_werr, + WERROR set_werr) +{ + struct security_descriptor *sd = NULL; + + if (!_test_GetKeySecurity(p, tctx, handle, NULL, get_werr, &sd)) { + return false; + } + + if (!_test_SetKeySecurity(p, tctx, handle, NULL, sd, set_werr)) { + return false; + } + + return true; +} + +static bool test_SecurityDescriptor(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "SecurityDescriptor get & set\n"); + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + if (!_test_GetSetSecurityDescriptor(p, tctx, &new_handle, + WERR_OK, WERR_OK)) { + ret = false; + } + + if (!test_CloseKey(b, tctx, &new_handle)) { + return false; + } + + return ret; +} + +static bool _test_SecurityDescriptor(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t access_mask, + const char *key, + WERROR open_werr, + WERROR get_werr, + WERROR set_werr) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, key, + REG_OPTION_NON_VOLATILE, + access_mask, + &new_handle, + open_werr), + "failed to open key"); + + if (!W_ERROR_IS_OK(open_werr)) { + return true; + } + + if (!_test_GetSetSecurityDescriptor(p, tctx, &new_handle, + get_werr, set_werr)) { + ret = false; + } + + if (!test_CloseKey(b, tctx, &new_handle)) { + return false; + } + + return ret; +} + +static bool test_dacl_trustee_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct dom_sid *sid) +{ + struct security_descriptor *sd = NULL; + int i; + + if (!test_GetKeySecurity(p, tctx, handle, &sd)) { + return false; + } + + if (!sd || !sd->dacl) { + return false; + } + + for (i = 0; i < sd->dacl->num_aces; i++) { + if (dom_sid_equal(&sd->dacl->aces[i].trustee, sid)) { + return true; + } + } + + return false; +} + +static bool _test_dacl_trustee_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + const struct dom_sid *sid) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + ret = test_dacl_trustee_present(p, tctx, &new_handle, sid); + + test_CloseKey(b, tctx, &new_handle); + + return ret; +} + +static bool test_sacl_trustee_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct dom_sid *sid) +{ + struct security_descriptor *sd = NULL; + int i; + uint32_t sec_info = SECINFO_SACL; + + if (!_test_GetKeySecurity(p, tctx, handle, &sec_info, WERR_OK, &sd)) { + return false; + } + + if (!sd || !sd->sacl) { + return false; + } + + for (i = 0; i < sd->sacl->num_aces; i++) { + if (dom_sid_equal(&sd->sacl->aces[i].trustee, sid)) { + return true; + } + } + + return false; +} + +static bool _test_sacl_trustee_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + const struct dom_sid *sid) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, key, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_SYSTEM_SECURITY, + &new_handle, + WERR_OK), + "failed to open key"); + + ret = test_sacl_trustee_present(p, tctx, &new_handle, sid); + + test_CloseKey(b, tctx, &new_handle); + + return ret; +} + +static bool test_owner_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct dom_sid *sid) +{ + struct security_descriptor *sd = NULL; + uint32_t sec_info = SECINFO_OWNER; + + if (!_test_GetKeySecurity(p, tctx, handle, &sec_info, WERR_OK, &sd)) { + return false; + } + + if (!sd || !sd->owner_sid) { + return false; + } + + return dom_sid_equal(sd->owner_sid, sid); +} + +static bool _test_owner_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + const struct dom_sid *sid) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + ret = test_owner_present(p, tctx, &new_handle, sid); + + test_CloseKey(b, tctx, &new_handle); + + return ret; +} + +static bool test_group_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct dom_sid *sid) +{ + struct security_descriptor *sd = NULL; + uint32_t sec_info = SECINFO_GROUP; + + if (!_test_GetKeySecurity(p, tctx, handle, &sec_info, WERR_OK, &sd)) { + return false; + } + + if (!sd || !sd->group_sid) { + return false; + } + + return dom_sid_equal(sd->group_sid, sid); +} + +static bool _test_group_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + const struct dom_sid *sid) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + ret = test_group_present(p, tctx, &new_handle, sid); + + test_CloseKey(b, tctx, &new_handle); + + return ret; +} + +static bool test_dacl_trustee_flags_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct dom_sid *sid, + uint8_t flags) +{ + struct security_descriptor *sd = NULL; + int i; + + if (!test_GetKeySecurity(p, tctx, handle, &sd)) { + return false; + } + + if (!sd || !sd->dacl) { + return false; + } + + for (i = 0; i < sd->dacl->num_aces; i++) { + if ((dom_sid_equal(&sd->dacl->aces[i].trustee, sid)) && + (sd->dacl->aces[i].flags == flags)) { + return true; + } + } + + return false; +} + +static bool test_dacl_ace_present(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const struct security_ace *ace) +{ + struct security_descriptor *sd = NULL; + int i; + + if (!test_GetKeySecurity(p, tctx, handle, &sd)) { + return false; + } + + if (!sd || !sd->dacl) { + return false; + } + + for (i = 0; i < sd->dacl->num_aces; i++) { + if (security_ace_equal(&sd->dacl->aces[i], ace)) { + return true; + } + } + + return false; +} + +static bool test_RestoreSecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + struct security_descriptor *sd) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + if (!test_SetKeySecurity(p, tctx, &new_handle, sd)) { + ret = false; + } + + if (!test_CloseKey(b, tctx, &new_handle)) { + ret = false; + } + + return ret; +} + +static bool test_BackupSecurity(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + struct security_descriptor **sd) +{ + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + if (!test_GetKeySecurity(p, tctx, &new_handle, sd)) { + ret = false; + } + + if (!test_CloseKey(b, tctx, &new_handle)) { + ret = false; + } + + return ret; +} + +static bool test_SecurityDescriptorInheritance(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + /* get sd + add ace SEC_ACE_FLAG_CONTAINER_INHERIT + set sd + get sd + check ace + add subkey + get sd + check ace + add subsubkey + get sd + check ace + del subsubkey + del subkey + reset sd + */ + + struct security_descriptor *sd = NULL; + struct security_descriptor *sd_orig = NULL; + struct security_ace *ace = NULL; + struct policy_handle new_handle; + bool ret = true; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *test_subkey_sd; + const char *test_subsubkey_sd; + + torture_comment(tctx, "SecurityDescriptor inheritance\n"); + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + if (!_test_GetKeySecurity(p, tctx, &new_handle, NULL, WERR_OK, &sd)) { + return false; + } + + sd_orig = security_descriptor_copy(tctx, sd); + if (sd_orig == NULL) { + return false; + } + + ace = security_ace_create(tctx, + TEST_SID, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_REQUIRED, + SEC_ACE_FLAG_CONTAINER_INHERIT); + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_add(sd, ace), + "failed to add ace"); + + /* FIXME: add further tests for these flags */ + sd->type |= SEC_DESC_DACL_AUTO_INHERIT_REQ | + SEC_DESC_SACL_AUTO_INHERITED; + + if (!test_SetKeySecurity(p, tctx, &new_handle, sd)) { + return false; + } + + torture_assert(tctx, + test_dacl_ace_present(p, tctx, &new_handle, ace), + "new ACE not present!"); + + if (!test_CloseKey(b, tctx, &new_handle)) { + return false; + } + + test_subkey_sd = talloc_asprintf(tctx, "%s\\%s", key, TEST_SUBKEY_SD); + + if (!test_CreateKey(b, tctx, handle, test_subkey_sd, NULL)) { + ret = false; + goto out; + } + + if (!test_OpenKey(b, tctx, handle, test_subkey_sd, &new_handle)) { + ret = false; + goto out; + } + + if (!test_dacl_ace_present(p, tctx, &new_handle, ace)) { + torture_comment(tctx, "inherited ACE not present!\n"); + ret = false; + goto out; + } + + test_subsubkey_sd = talloc_asprintf(tctx, "%s\\%s", key, TEST_SUBSUBKEY_SD); + + test_CloseKey(b, tctx, &new_handle); + if (!test_CreateKey(b, tctx, handle, test_subsubkey_sd, NULL)) { + ret = false; + goto out; + } + + if (!test_OpenKey(b, tctx, handle, test_subsubkey_sd, &new_handle)) { + ret = false; + goto out; + } + + if (!test_dacl_ace_present(p, tctx, &new_handle, ace)) { + torture_comment(tctx, "inherited ACE not present!\n"); + ret = false; + goto out; + } + + out: + test_CloseKey(b, tctx, &new_handle); + test_Cleanup(b, tctx, handle, test_subkey_sd); + test_RestoreSecurity(p, tctx, handle, key, sd_orig); + + return ret; +} + +static bool test_SecurityDescriptorBlockInheritance(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + /* get sd + add ace SEC_ACE_FLAG_NO_PROPAGATE_INHERIT + set sd + add subkey/subkey + get sd + check ace + get sd from subkey + check ace + del subkey/subkey + del subkey + reset sd + */ + + struct security_descriptor *sd = NULL; + struct security_descriptor *sd_orig = NULL; + struct security_ace *ace = NULL; + struct policy_handle new_handle; + struct dom_sid *sid = NULL; + bool ret = true; + uint8_t ace_flags = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *test_subkey_sd; + const char *test_subsubkey_sd; + + torture_comment(tctx, "SecurityDescriptor inheritance block\n"); + + if (!test_OpenKey(b, tctx, handle, key, &new_handle)) { + return false; + } + + if (!_test_GetKeySecurity(p, tctx, &new_handle, NULL, WERR_OK, &sd)) { + return false; + } + + sd_orig = security_descriptor_copy(tctx, sd); + if (sd_orig == NULL) { + return false; + } + + ace = security_ace_create(tctx, + TEST_SID, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_STD_REQUIRED, + SEC_ACE_FLAG_CONTAINER_INHERIT | + SEC_ACE_FLAG_NO_PROPAGATE_INHERIT); + + torture_assert_ntstatus_ok(tctx, + security_descriptor_dacl_add(sd, ace), + "failed to add ace"); + + if (!_test_SetKeySecurity(p, tctx, &new_handle, NULL, sd, WERR_OK)) { + return false; + } + + torture_assert(tctx, + test_dacl_ace_present(p, tctx, &new_handle, ace), + "new ACE not present!"); + + if (!test_CloseKey(b, tctx, &new_handle)) { + return false; + } + + test_subkey_sd = talloc_asprintf(tctx, "%s\\%s", key, TEST_SUBKEY_SD); + test_subsubkey_sd = talloc_asprintf(tctx, "%s\\%s", key, TEST_SUBSUBKEY_SD); + + if (!test_CreateKey(b, tctx, handle, test_subsubkey_sd, NULL)) { + return false; + } + + if (!test_OpenKey(b, tctx, handle, test_subsubkey_sd, &new_handle)) { + ret = false; + goto out; + } + + if (test_dacl_ace_present(p, tctx, &new_handle, ace)) { + torture_comment(tctx, "inherited ACE present but should not!\n"); + ret = false; + goto out; + } + + sid = dom_sid_parse_talloc(tctx, TEST_SID); + if (sid == NULL) { + return false; + } + + if (test_dacl_trustee_present(p, tctx, &new_handle, sid)) { + torture_comment(tctx, "inherited trustee SID present but should not!\n"); + ret = false; + goto out; + } + + test_CloseKey(b, tctx, &new_handle); + + if (!test_OpenKey(b, tctx, handle, test_subkey_sd, &new_handle)) { + ret = false; + goto out; + } + + if (test_dacl_ace_present(p, tctx, &new_handle, ace)) { + torture_comment(tctx, "inherited ACE present but should not!\n"); + ret = false; + goto out; + } + + if (!test_dacl_trustee_flags_present(p, tctx, &new_handle, sid, ace_flags)) { + torture_comment(tctx, "inherited trustee SID with flags 0x%02x not present!\n", + ace_flags); + ret = false; + goto out; + } + + out: + test_CloseKey(b, tctx, &new_handle); + test_Cleanup(b, tctx, handle, test_subkey_sd); + test_RestoreSecurity(p, tctx, handle, key, sd_orig); + + return ret; +} + +static bool test_SecurityDescriptorsMasks(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + bool ret = true; + int i; + + struct winreg_mask_result_table { + uint32_t access_mask; + WERROR open_werr; + WERROR get_werr; + WERROR set_werr; + } sd_mask_tests[] = { + { 0, + WERR_ACCESS_DENIED, WERR_FILE_NOT_FOUND, WERR_FOOBAR }, + { SEC_FLAG_MAXIMUM_ALLOWED, + WERR_OK, WERR_OK, WERR_OK }, + { SEC_STD_WRITE_DAC, + WERR_OK, WERR_ACCESS_DENIED, WERR_FOOBAR }, + { SEC_FLAG_SYSTEM_SECURITY, + WERR_OK, WERR_ACCESS_DENIED, WERR_FOOBAR } + }; + + /* FIXME: before this test can ever run successfully we need a way to + * correctly read a NULL security_descritpor in ndr, get the required + * length, requery, etc. + */ + + return true; + + for (i=0; i < ARRAY_SIZE(sd_mask_tests); i++) { + + torture_comment(tctx, + "SecurityDescriptor get & set with access_mask: 0x%08x\n", + sd_mask_tests[i].access_mask); + torture_comment(tctx, + "expecting: open %s, get: %s, set: %s\n", + win_errstr(sd_mask_tests[i].open_werr), + win_errstr(sd_mask_tests[i].get_werr), + win_errstr(sd_mask_tests[i].set_werr)); + + if (_test_SecurityDescriptor(p, tctx, handle, + sd_mask_tests[i].access_mask, key, + sd_mask_tests[i].open_werr, + sd_mask_tests[i].get_werr, + sd_mask_tests[i].set_werr)) { + ret = false; + } + } + + return ret; +} + +typedef bool (*secinfo_verify_fn)(struct dcerpc_pipe *, + struct torture_context *, + struct policy_handle *, + const char *, + const struct dom_sid *); + +static bool test_SetSecurityDescriptor_SecInfo(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + const char *test, + uint32_t access_mask, + uint32_t sec_info, + struct security_descriptor *sd, + WERROR set_werr, + bool expect_present, + bool (*fn) (struct dcerpc_pipe *, + struct torture_context *, + struct policy_handle *, + const char *, + const struct dom_sid *), + const struct dom_sid *sid) +{ + struct policy_handle new_handle; + struct dcerpc_binding_handle *b = p->binding_handle; + + torture_comment(tctx, "SecurityDescriptor (%s) sets for secinfo: " + "0x%08x, access_mask: 0x%08x\n", + test, sec_info, access_mask); + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, key, + REG_OPTION_NON_VOLATILE, + access_mask, + &new_handle, + WERR_OK), + "failed to open key"); + + if (!_test_SetKeySecurity(p, tctx, &new_handle, &sec_info, + sd, + set_werr)) { + torture_warning(tctx, + "SetKeySecurity with secinfo: 0x%08x has failed\n", + sec_info); + smb_panic(""); + test_CloseKey(b, tctx, &new_handle); + return false; + } + + test_CloseKey(b, tctx, &new_handle); + + if (W_ERROR_IS_OK(set_werr)) { + bool present; + present = fn(p, tctx, handle, key, sid); + if ((expect_present) && (!present)) { + torture_warning(tctx, + "%s sid is not present!\n", + test); + return false; + } + if ((!expect_present) && (present)) { + torture_warning(tctx, + "%s sid is present but not expected!\n", + test); + return false; + } + } + + return true; +} + +static bool test_SecurityDescriptorsSecInfo(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + struct security_descriptor *sd_orig = NULL; + struct dom_sid *sid = NULL; + bool ret = true; + int i, a; + + struct security_descriptor *sd_owner = + security_descriptor_dacl_create(tctx, + 0, + TEST_SID, NULL, NULL); + + struct security_descriptor *sd_group = + security_descriptor_dacl_create(tctx, + 0, + NULL, TEST_SID, NULL); + + struct security_descriptor *sd_dacl = + security_descriptor_dacl_create(tctx, + 0, + NULL, NULL, + TEST_SID, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + 0, + SID_NT_AUTHENTICATED_USERS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, + 0, + NULL); + + struct security_descriptor *sd_sacl = + security_descriptor_sacl_create(tctx, + 0, + NULL, NULL, + TEST_SID, + SEC_ACE_TYPE_SYSTEM_AUDIT, + SEC_GENERIC_ALL, + SEC_ACE_FLAG_SUCCESSFUL_ACCESS, + NULL); + + struct winreg_secinfo_table { + struct security_descriptor *sd; + uint32_t sec_info; + WERROR set_werr; + bool sid_present; + secinfo_verify_fn fn; + }; + + struct winreg_secinfo_table sec_info_owner_tests[] = { + { + .sd = sd_owner, + .sec_info = 0, + .set_werr = WERR_OK, + .sid_present = false, + .fn = (secinfo_verify_fn)_test_owner_present, + }, + { + .sd = sd_owner, + .sec_info = SECINFO_OWNER, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_owner_present, + }, + { + .sd = sd_owner, + .sec_info = SECINFO_GROUP, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_owner, + .sec_info = SECINFO_DACL, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_owner_present, + }, + { + .sd = sd_owner, + .sec_info = SECINFO_SACL, + .set_werr = WERR_ACCESS_DENIED, + .sid_present = false, + }, + }; + + uint32_t sd_owner_good_access_masks[] = { + SEC_FLAG_MAXIMUM_ALLOWED, + /* SEC_STD_WRITE_OWNER, */ + }; + + struct winreg_secinfo_table sec_info_group_tests[] = { + { + .sd = sd_group, + .sec_info = 0, + .set_werr = WERR_OK, + .sid_present = false, + .fn = (secinfo_verify_fn)_test_group_present, + }, + { + .sd = sd_group, + .sec_info = SECINFO_OWNER, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_group, + .sec_info = SECINFO_GROUP, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_group_present, + }, + { + .sd = sd_group, + .sec_info = SECINFO_DACL, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_group_present, + }, + { + .sd = sd_group, + .sec_info = SECINFO_SACL, + .set_werr = WERR_ACCESS_DENIED, + .sid_present = false, + }, + }; + + uint32_t sd_group_good_access_masks[] = { + SEC_FLAG_MAXIMUM_ALLOWED, + }; + + struct winreg_secinfo_table sec_info_dacl_tests[] = { + { + .sd = sd_dacl, + .sec_info = 0, + .set_werr = WERR_OK, + .sid_present = false, + .fn = (secinfo_verify_fn)_test_dacl_trustee_present, + }, + { + .sd = sd_dacl, + .sec_info = SECINFO_OWNER, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_dacl, + .sec_info = SECINFO_GROUP, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_dacl, + .sec_info = SECINFO_DACL, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_dacl_trustee_present + }, + { + .sd = sd_dacl, + .sec_info = SECINFO_SACL, + .set_werr = WERR_ACCESS_DENIED, + .sid_present = false, + }, + }; + + uint32_t sd_dacl_good_access_masks[] = { + SEC_FLAG_MAXIMUM_ALLOWED, + SEC_STD_WRITE_DAC, + }; + + struct winreg_secinfo_table sec_info_sacl_tests[] = { + { + .sd = sd_sacl, + .sec_info = 0, + .set_werr = WERR_OK, + .sid_present = false, + .fn = (secinfo_verify_fn)_test_sacl_trustee_present, + }, + { + .sd = sd_sacl, + .sec_info = SECINFO_OWNER, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_sacl, + .sec_info = SECINFO_GROUP, + .set_werr = WERR_INVALID_PARAMETER, + .sid_present = false, + }, + { + .sd = sd_sacl, + .sec_info = SECINFO_DACL, + .set_werr = WERR_OK, + .sid_present = false, + .fn = (secinfo_verify_fn)_test_sacl_trustee_present, + }, + { + .sd = sd_sacl, + .sec_info = SECINFO_SACL, + .set_werr = WERR_OK, + .sid_present = true, + .fn = (secinfo_verify_fn)_test_sacl_trustee_present, + }, + }; + + uint32_t sd_sacl_good_access_masks[] = { + SEC_FLAG_MAXIMUM_ALLOWED | SEC_FLAG_SYSTEM_SECURITY, + /* SEC_FLAG_SYSTEM_SECURITY, */ + }; + + sid = dom_sid_parse_talloc(tctx, TEST_SID); + if (sid == NULL) { + return false; + } + + if (!test_BackupSecurity(p, tctx, handle, key, &sd_orig)) { + return false; + } + + /* OWNER */ + + for (i=0; i < ARRAY_SIZE(sec_info_owner_tests); i++) { + + for (a=0; a < ARRAY_SIZE(sd_owner_good_access_masks); a++) { + + if (!test_SetSecurityDescriptor_SecInfo(p, tctx, handle, + key, + "OWNER", + sd_owner_good_access_masks[a], + sec_info_owner_tests[i].sec_info, + sec_info_owner_tests[i].sd, + sec_info_owner_tests[i].set_werr, + sec_info_owner_tests[i].sid_present, + sec_info_owner_tests[i].fn, + sid)) + { + torture_comment(tctx, "test_SetSecurityDescriptor_SecInfo failed for OWNER\n"); + ret = false; + goto out; + } + } + } + + /* GROUP */ + + for (i=0; i < ARRAY_SIZE(sec_info_group_tests); i++) { + + for (a=0; a < ARRAY_SIZE(sd_group_good_access_masks); a++) { + + if (!test_SetSecurityDescriptor_SecInfo(p, tctx, handle, + key, + "GROUP", + sd_group_good_access_masks[a], + sec_info_group_tests[i].sec_info, + sec_info_group_tests[i].sd, + sec_info_group_tests[i].set_werr, + sec_info_group_tests[i].sid_present, + sec_info_group_tests[i].fn, + sid)) + { + torture_comment(tctx, "test_SetSecurityDescriptor_SecInfo failed for GROUP\n"); + ret = false; + goto out; + } + } + } + + /* DACL */ + + for (i=0; i < ARRAY_SIZE(sec_info_dacl_tests); i++) { + + for (a=0; a < ARRAY_SIZE(sd_dacl_good_access_masks); a++) { + + if (!test_SetSecurityDescriptor_SecInfo(p, tctx, handle, + key, + "DACL", + sd_dacl_good_access_masks[a], + sec_info_dacl_tests[i].sec_info, + sec_info_dacl_tests[i].sd, + sec_info_dacl_tests[i].set_werr, + sec_info_dacl_tests[i].sid_present, + sec_info_dacl_tests[i].fn, + sid)) + { + torture_comment(tctx, "test_SetSecurityDescriptor_SecInfo failed for DACL\n"); + ret = false; + goto out; + } + } + } + + /* SACL */ + + for (i=0; i < ARRAY_SIZE(sec_info_sacl_tests); i++) { + + for (a=0; a < ARRAY_SIZE(sd_sacl_good_access_masks); a++) { + + if (!test_SetSecurityDescriptor_SecInfo(p, tctx, handle, + key, + "SACL", + sd_sacl_good_access_masks[a], + sec_info_sacl_tests[i].sec_info, + sec_info_sacl_tests[i].sd, + sec_info_sacl_tests[i].set_werr, + sec_info_sacl_tests[i].sid_present, + sec_info_sacl_tests[i].fn, + sid)) + { + torture_comment(tctx, "test_SetSecurityDescriptor_SecInfo failed for SACL\n"); + ret = false; + goto out; + } + } + } + + out: + test_RestoreSecurity(p, tctx, handle, key, sd_orig); + + return ret; +} + +static bool test_SecurityDescriptors(struct dcerpc_pipe *p, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key) +{ + bool ret = true; + + if (!test_SecurityDescriptor(p, tctx, handle, key)) { + torture_comment(tctx, "test_SecurityDescriptor failed\n"); + ret = false; + } + + if (!test_SecurityDescriptorInheritance(p, tctx, handle, key)) { + torture_comment(tctx, "test_SecurityDescriptorInheritance failed\n"); + ret = false; + } + + if (!test_SecurityDescriptorBlockInheritance(p, tctx, handle, key)) { + torture_comment(tctx, "test_SecurityDescriptorBlockInheritance failed\n"); + ret = false; + } + + if (!test_SecurityDescriptorsSecInfo(p, tctx, handle, key)) { + torture_comment(tctx, "test_SecurityDescriptorsSecInfo failed\n"); + ret = false; + } + + if (!test_SecurityDescriptorsMasks(p, tctx, handle, key)) { + torture_comment(tctx, "test_SecurityDescriptorsMasks failed\n"); + ret = false; + } + + return ret; +} + +static bool test_DeleteKey_opts(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *key, + WERROR expected_result) +{ + struct winreg_DeleteKey r; + + torture_comment(tctx, "Testing DeleteKey(%s)\n", key); + + r.in.handle = handle; + init_winreg_String(&r.in.key, key); + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_DeleteKey_r(b, tctx, &r), + "Delete Key failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_result, + "DeleteKey failed"); + + return true; +} + +static bool test_DeleteKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, const char *key) +{ + return test_DeleteKey_opts(b, tctx, handle, key, WERR_OK); +} + +static bool test_QueryInfoKey(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + char *kclass, + uint32_t *pmax_valnamelen, + uint32_t *pmax_valbufsize) +{ + struct winreg_QueryInfoKey r; + uint32_t num_subkeys, max_subkeylen, max_classlen, + num_values, max_valnamelen, max_valbufsize, + secdescsize; + NTTIME last_changed_time; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.out.num_subkeys = &num_subkeys; + r.out.max_subkeylen = &max_subkeylen; + r.out.max_classlen = &max_classlen; + r.out.num_values = &num_values; + r.out.max_valnamelen = &max_valnamelen; + r.out.max_valbufsize = &max_valbufsize; + r.out.secdescsize = &secdescsize; + r.out.last_changed_time = &last_changed_time; + + r.out.classname = talloc(tctx, struct winreg_String); + + r.in.classname = talloc(tctx, struct winreg_String); + init_winreg_String(r.in.classname, kclass); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryInfoKey_r(b, tctx, &r), + "QueryInfoKey failed"); + + torture_assert_werr_ok(tctx, r.out.result, "QueryInfoKey failed"); + + if (pmax_valnamelen) { + *pmax_valnamelen = max_valnamelen; + } + + if (pmax_valbufsize) { + *pmax_valbufsize = max_valbufsize; + } + + return true; +} + +static bool test_SetValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *value_name, + enum winreg_Type type, + uint8_t *data, + uint32_t size) +{ + struct winreg_SetValue r; + struct winreg_String name; + + torture_comment(tctx, "Testing SetValue(%s), type: %s, offered: 0x%08x)\n", + value_name, str_regtype(type), size); + + init_winreg_String(&name, value_name); + + r.in.handle = handle; + r.in.name = name; + r.in.type = type; + r.in.data = data; + r.in.size = size; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_SetValue_r(b, tctx, &r), + "winreg_SetValue failed"); + torture_assert_werr_ok(tctx, r.out.result, + "winreg_SetValue failed"); + + return true; +} + +static bool test_DeleteValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *value_name) +{ + struct winreg_DeleteValue r; + struct winreg_String value; + + torture_comment(tctx, "Testing DeleteValue(%s)\n", value_name); + + init_winreg_String(&value, value_name); + + r.in.handle = handle; + r.in.value = value; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_DeleteValue_r(b, tctx, &r), + "winreg_DeleteValue failed"); + torture_assert_werr_ok(tctx, r.out.result, + "winreg_DeleteValue failed"); + + return true; +} + +static bool test_key(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, int depth, + bool test_security); + +static bool test_EnumKey(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, int depth, + bool test_security) +{ + struct winreg_EnumKey r; + struct winreg_StringBuf kclass, name; + NTSTATUS status; + NTTIME t = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + kclass.name = ""; + kclass.size = 1024; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.in.enum_index = 0; + r.in.name = &name; + r.in.keyclass = &kclass; + r.out.name = &name; + r.in.last_changed_time = &t; + + do { + name.name = NULL; + name.size = 1024; + + status = dcerpc_winreg_EnumKey_r(b, tctx, &r); + + if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(r.out.result)) { + struct policy_handle key_handle; + + torture_comment(tctx, "EnumKey: %d: %s\n", + r.in.enum_index, + r.out.name->name); + + if (!test_OpenKey(b, tctx, handle, r.out.name->name, + &key_handle)) { + } else { + test_key(p, tctx, &key_handle, + depth + 1, test_security); + } + } + + r.in.enum_index++; + + } while (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(r.out.result)); + + torture_assert_ntstatus_ok(tctx, status, "EnumKey failed"); + + if (!W_ERROR_IS_OK(r.out.result) && + !W_ERROR_EQUAL(r.out.result, WERR_NO_MORE_ITEMS)) { + torture_fail(tctx, "EnumKey failed"); + } + + return true; +} + +static bool test_QueryMultipleValues(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *valuename) +{ + struct winreg_QueryMultipleValues r; + uint32_t bufsize=0; + + ZERO_STRUCT(r); + + r.in.key_handle = handle; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, 1); + r.in.values_in[0].ve_valuename = talloc(tctx, struct winreg_ValNameBuf); + r.in.values_in[0].ve_valuename->name = valuename; + /* size needs to be set manually for winreg_ValNameBuf */ + r.in.values_in[0].ve_valuename->size = strlen_m_term(valuename)*2; + + r.in.num_values = 1; + r.in.buffer_size = r.out.buffer_size = talloc(tctx, uint32_t); + *r.in.buffer_size = bufsize; + do { + *r.in.buffer_size = bufsize; + r.in.buffer = r.out.buffer = talloc_zero_array(tctx, uint8_t, + *r.in.buffer_size); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues_r(b, tctx, &r), + "QueryMultipleValues failed"); + + talloc_free(r.in.buffer); + bufsize += 0x20; + } while (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)); + + torture_assert_werr_ok(tctx, r.out.result, "QueryMultipleValues failed"); + + return true; +} + +static bool test_QueryMultipleValues_full(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_values, + const char * const *valuenames, + bool existing_value) +{ + struct winreg_QueryMultipleValues r; + uint32_t bufsize = 0; + int i; + + torture_comment(tctx, "Testing QueryMultipleValues\n"); + + ZERO_STRUCT(r); + + r.in.key_handle = handle; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, 0); + r.in.buffer_size = r.out.buffer_size = &bufsize; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues_r(b, tctx, &r), + "QueryMultipleValues failed"); + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues failed"); + + /* this test crashes w2k8 remote registry */ +#if 0 + r.in.num_values = num_values; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, num_values); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues_r(b, tctx, &r), + "QueryMultipleValues failed"); + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues failed"); +#endif + r.in.num_values = num_values; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, num_values); + for (i=0; i < r.in.num_values; i++) { + r.in.values_in[i].ve_valuename = talloc_zero(tctx, struct winreg_ValNameBuf); + r.in.values_in[i].ve_valuename->name = talloc_strdup(tctx, valuenames[i]); + r.in.values_in[i].ve_valuename->size = strlen_m_term(r.in.values_in[i].ve_valuename->name)*2; + } + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues_r(b, tctx, &r), + "QueryMultipleValues failed"); + torture_assert_werr_equal(tctx, r.out.result, existing_value ? WERR_MORE_DATA : WERR_FILE_NOT_FOUND, + "QueryMultipleValues failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_FILE_NOT_FOUND)) { + return true; + } + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + *r.in.buffer_size = 0xff; + r.in.buffer = r.out.buffer = talloc_zero_array(tctx, uint8_t, *r.in.buffer_size); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues_r(b, tctx, &r), + "QueryMultipleValues failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues failed"); + + return true; +} + + +static bool test_QueryMultipleValues2_full(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + uint32_t num_values, + const char * const *valuenames, + bool existing_value) +{ + struct winreg_QueryMultipleValues2 r; + uint32_t offered = 0, needed; + int i; + + torture_comment(tctx, "Testing QueryMultipleValues2\n"); + + ZERO_STRUCT(r); + + r.in.key_handle = handle; + r.in.offered = &offered; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, 0); + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues2 failed"); + + /* this test crashes w2k8 remote registry */ +#if 0 + r.in.num_values = num_values; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, num_values); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues2 failed"); +#endif + r.in.num_values = num_values; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, num_values); + for (i=0; i < r.in.num_values; i++) { + r.in.values_in[i].ve_valuename = talloc_zero(tctx, struct winreg_ValNameBuf); + r.in.values_in[i].ve_valuename->name = talloc_strdup(tctx, valuenames[i]); + r.in.values_in[i].ve_valuename->size = strlen_m_term(r.in.values_in[i].ve_valuename->name)*2; + } + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + torture_assert_werr_equal(tctx, r.out.result, existing_value ? WERR_MORE_DATA : WERR_FILE_NOT_FOUND, + "QueryMultipleValues2 failed"); + + if (W_ERROR_EQUAL(r.out.result, WERR_FILE_NOT_FOUND)) { + return true; + } + + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + *r.in.offered = *r.out.needed; + r.in.buffer = r.out.buffer = talloc_zero_array(tctx, uint8_t, *r.in.offered); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues2 failed"); + + return true; +} + +static bool test_QueryMultipleValues2(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *valuename) +{ + struct winreg_QueryMultipleValues2 r; + uint32_t offered = 0, needed; + + ZERO_STRUCT(r); + + r.in.key_handle = handle; + r.in.values_in = r.out.values_out = talloc_zero_array(tctx, struct QueryMultipleValue, 1); + r.in.values_in[0].ve_valuename = talloc(tctx, struct winreg_ValNameBuf); + r.in.values_in[0].ve_valuename->name = valuename; + /* size needs to be set manually for winreg_ValNameBuf */ + r.in.values_in[0].ve_valuename->size = strlen_m_term(valuename)*2; + + r.in.num_values = 1; + r.in.offered = &offered; + r.out.needed = &needed; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + if (W_ERROR_EQUAL(r.out.result, WERR_MORE_DATA)) { + *r.in.offered = *r.out.needed; + r.in.buffer = r.out.buffer = talloc_zero_array(tctx, uint8_t, *r.in.offered); + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_QueryMultipleValues2_r(b, tctx, &r), + "QueryMultipleValues2 failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, + "QueryMultipleValues2 failed"); + + return true; +} + +static bool test_QueryValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *valuename) +{ + struct winreg_QueryValue r; + NTSTATUS status; + enum winreg_Type zero_type = 0; + uint32_t offered = 0xfff; + uint32_t zero = 0; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.in.data = NULL; + r.in.value_name = talloc_zero(tctx, struct winreg_String); + r.in.value_name->name = valuename; + r.in.type = &zero_type; + r.in.data_size = &offered; + r.in.data_length = &zero; + + status = dcerpc_winreg_QueryValue_r(b, tctx, &r); + if (NT_STATUS_IS_ERR(status)) { + torture_fail(tctx, "QueryValue failed"); + } + + torture_assert_werr_ok(tctx, r.out.result, "QueryValue failed"); + + return true; +} + +static bool test_QueryValue_full(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, + const char *valuename, + bool existing_value) +{ + struct winreg_QueryValue r; + struct winreg_String value_name; + enum winreg_Type type = REG_NONE; + uint32_t data_size = 0; + uint32_t real_data_size = 0; + uint32_t data_length = 0; + uint8_t *data = NULL; + WERROR expected_error = WERR_FILE_NOT_FOUND; + const char *errmsg_nonexisting = "expected WERR_FILE_NOT_FOUND for nonexisting value"; + + if (valuename == NULL) { + expected_error = WERR_INVALID_PARAMETER; + errmsg_nonexisting = "expected WERR_INVALID_PARAMETER for NULL valuename"; + } + + ZERO_STRUCT(r); + + init_winreg_String(&value_name, NULL); + + torture_comment(tctx, "Testing QueryValue(%s)\n", valuename); + + r.in.handle = handle; + r.in.value_name = &value_name; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), "QueryValue failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "expected WERR_INVALID_PARAMETER for NULL winreg_String.name"); + + init_winreg_String(&value_name, valuename); + r.in.value_name = &value_name; + + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "expected WERR_INVALID_PARAMETER for missing type length and size"); + + r.in.type = &type; + r.out.type = &type; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "expected WERR_INVALID_PARAMETER for missing length and size"); + + r.in.data_length = &data_length; + r.out.data_length = &data_length; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_PARAMETER, + "expected WERR_INVALID_PARAMETER for missing size"); + + r.in.data_size = &data_size; + r.out.data_size = &data_size; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + if (existing_value) { + torture_assert_werr_ok(tctx, r.out.result, + "QueryValue failed"); + } else { + torture_assert_werr_equal(tctx, r.out.result, expected_error, + errmsg_nonexisting); + } + + real_data_size = *r.out.data_size; + + data = talloc_zero_array(tctx, uint8_t, 0); + r.in.data = data; + r.out.data = data; + *r.in.data_size = 0; + *r.out.data_size = 0; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + if (existing_value) { + torture_assert_werr_equal(tctx, r.out.result, WERR_MORE_DATA, + "expected WERR_MORE_DATA for query with too small buffer"); + } else { + torture_assert_werr_equal(tctx, r.out.result, expected_error, + errmsg_nonexisting); + } + + data = talloc_zero_array(tctx, uint8_t, real_data_size); + r.in.data = data; + r.out.data = data; + r.in.data_size = &real_data_size; + r.out.data_size = &real_data_size; + torture_assert_ntstatus_ok(tctx, dcerpc_winreg_QueryValue_r(b, tctx, &r), + "QueryValue failed"); + if (existing_value) { + torture_assert_werr_ok(tctx, r.out.result, + "QueryValue failed"); + } else { + torture_assert_werr_equal(tctx, r.out.result, expected_error, + errmsg_nonexisting); + } + + return true; +} + +static bool test_EnumValue(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle, int max_valnamelen, + int max_valbufsize) +{ + struct winreg_EnumValue r; + enum winreg_Type type = 0; + uint32_t size = max_valbufsize, zero = 0; + bool ret = true; + uint8_t *data = NULL; + struct winreg_ValNameBuf name; + char n = '\0'; + + ZERO_STRUCT(r); + r.in.handle = handle; + r.in.enum_index = 0; + r.in.name = &name; + r.out.name = &name; + r.in.type = &type; + r.in.length = &zero; + r.in.size = &size; + + do { + name.name = &n; + name.size = max_valnamelen + 2; + name.length = 0; + + data = NULL; + if (size) { + data = talloc_array(tctx, uint8_t, size); + } + r.in.value = data; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_EnumValue_r(b, tctx, &r), + "EnumValue failed"); + + if (W_ERROR_IS_OK(r.out.result)) { + ret &= test_QueryValue(b, tctx, handle, + r.out.name->name); + ret &= test_QueryMultipleValues(b, tctx, handle, + r.out.name->name); + ret &= test_QueryMultipleValues2(b, tctx, handle, + r.out.name->name); + } + + talloc_free(data); + + r.in.enum_index++; + } while (W_ERROR_IS_OK(r.out.result)); + + torture_assert_werr_equal(tctx, r.out.result, WERR_NO_MORE_ITEMS, + "EnumValue failed"); + + return ret; +} + +static bool test_AbortSystemShutdown(struct dcerpc_binding_handle *b, + struct torture_context *tctx) +{ + struct winreg_AbortSystemShutdown r; + uint16_t server = 0x0; + + ZERO_STRUCT(r); + r.in.server = &server; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_AbortSystemShutdown_r(b, tctx, &r), + "AbortSystemShutdown failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "AbortSystemShutdown failed"); + + return true; +} + +static bool test_InitiateSystemShutdown(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct winreg_InitiateSystemShutdown r; + uint16_t hostname = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + r.in.hostname = &hostname; + r.in.message = talloc(tctx, struct lsa_StringLarge); + init_lsa_StringLarge(r.in.message, "spottyfood"); + r.in.force_apps = 1; + r.in.timeout = 30; + r.in.do_reboot = 1; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_InitiateSystemShutdown_r(b, tctx, &r), + "InitiateSystemShutdown failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "InitiateSystemShutdown failed"); + + return test_AbortSystemShutdown(b, tctx); +} + + +static bool test_InitiateSystemShutdownEx(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + struct winreg_InitiateSystemShutdownEx r; + uint16_t hostname = 0x0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + r.in.hostname = &hostname; + r.in.message = talloc(tctx, struct lsa_StringLarge); + init_lsa_StringLarge(r.in.message, "spottyfood"); + r.in.force_apps = 1; + r.in.timeout = 30; + r.in.do_reboot = 1; + r.in.reason = 0; + + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_InitiateSystemShutdownEx_r(b, tctx, &r), + "InitiateSystemShutdownEx failed"); + + torture_assert_werr_ok(tctx, r.out.result, + "InitiateSystemShutdownEx failed"); + + return test_AbortSystemShutdown(b, tctx); +} +#define MAX_DEPTH 2 /* Only go this far down the tree */ + +static bool test_key(struct dcerpc_pipe *p, struct torture_context *tctx, + struct policy_handle *handle, int depth, + bool test_security) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + uint32_t max_valnamelen = 0; + uint32_t max_valbufsize = 0; + + if (depth == MAX_DEPTH) + return true; + + if (!test_QueryInfoKey(b, tctx, handle, NULL, + &max_valnamelen, &max_valbufsize)) { + } + + if (!test_NotifyChangeKeyValue(b, tctx, handle)) { + } + + if (test_security && !test_GetKeySecurity(p, tctx, handle, NULL)) { + } + + if (!test_EnumKey(p, tctx, handle, depth, test_security)) { + } + + if (!test_EnumValue(b, tctx, handle, max_valnamelen, max_valbufsize)) { + } + + if (!test_EnumValue(b, tctx, handle, max_valnamelen, 0xFFFF)) { + } + + test_CloseKey(b, tctx, handle); + + return true; +} + +static bool test_SetValue_simple(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + const char *value_name = TEST_VALUE; + uint32_t value = 0x12345678; + uint64_t value2 = 0x12345678; + const char *string = "torture"; + const char *array[2]; + DATA_BLOB blob; + enum winreg_Type types[] = { + REG_DWORD, + REG_DWORD_BIG_ENDIAN, + REG_QWORD, + REG_BINARY, + REG_SZ, + REG_MULTI_SZ + }; + int t; + + array[0] = "array0"; + array[1] = NULL; + + torture_comment(tctx, "Testing SetValue (standard formats)\n"); + + for (t=0; t < ARRAY_SIZE(types); t++) { + + enum winreg_Type w_type; + uint32_t w_size, w_length; + uint8_t *w_data; + + switch (types[t]) { + case REG_DWORD: + case REG_DWORD_BIG_ENDIAN: + blob = data_blob_talloc_zero(tctx, 4); + SIVAL(blob.data, 0, value); + break; + case REG_QWORD: + blob = data_blob_talloc_zero(tctx, 8); + SBVAL(blob.data, 0, value2); + break; + case REG_BINARY: + blob = data_blob_string_const("binary_blob"); + break; + case REG_SZ: + torture_assert(tctx, push_reg_sz(tctx, &blob, string), "failed to push REG_SZ"); + break; + case REG_MULTI_SZ: + torture_assert(tctx, push_reg_multi_sz(tctx, &blob, array), "failed to push REG_MULTI_SZ"); + break; + default: + break; + } + + torture_assert(tctx, + test_SetValue(b, tctx, handle, value_name, types[t], blob.data, blob.length), + "test_SetValue failed"); + torture_assert(tctx, + test_QueryValue_full(b, tctx, handle, value_name, true), + talloc_asprintf(tctx, "test_QueryValue_full for %s value failed", value_name)); + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, handle, value_name, &w_type, &w_size, &w_length, &w_data), + "test_winreg_QueryValue failed"); + torture_assert(tctx, + test_DeleteValue(b, tctx, handle, value_name), + "test_DeleteValue failed"); + + torture_assert_int_equal(tctx, w_type, types[t], "winreg type mismatch"); + torture_assert_int_equal(tctx, w_size, blob.length, "winreg size mismatch"); + torture_assert_int_equal(tctx, w_length, blob.length, "winreg length mismatch"); + torture_assert_mem_equal(tctx, w_data, blob.data, blob.length, "winreg buffer mismatch"); + } + + torture_comment(tctx, "Testing SetValue (standard formats) succeeded\n"); + + return true; +} + +static bool test_SetValue_values(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + DATA_BLOB blob; + const char *values[] = { + "torture_value", + "torture value", + "torture,value", + "torture;value", + "torture/value", + "torture\\value", + "torture_value_name", + "torture value name", + "torture,value,name", + "torture;value;name", + "torture/value/name", + "torture\\value\\name", + }; + int i; + + torture_comment(tctx, "Testing SetValue (values)\n"); + + for (i=0; i < ARRAY_SIZE(values); i++) { + + enum winreg_Type w_type; + uint32_t w_size, w_length; + uint8_t *w_data; + + blob = data_blob_talloc(tctx, NULL, 32); + + generate_random_buffer(blob.data, 32); + + torture_assert(tctx, + test_SetValue(b, tctx, handle, values[i], REG_BINARY, blob.data, blob.length), + "test_SetValue failed"); + torture_assert(tctx, + test_QueryValue_full(b, tctx, handle, values[i], true), + talloc_asprintf(tctx, "test_QueryValue_full for %s value failed", values[i])); + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, handle, values[i], &w_type, &w_size, &w_length, &w_data), + "test_winreg_QueryValue failed"); + torture_assert(tctx, + test_DeleteValue(b, tctx, handle, values[i]), + "test_DeleteValue failed"); + + torture_assert_int_equal(tctx, w_type, REG_BINARY, "winreg type mismatch"); + torture_assert_int_equal(tctx, w_size, blob.length, "winreg size mismatch"); + torture_assert_int_equal(tctx, w_length, blob.length, "winreg length mismatch"); + torture_assert_mem_equal(tctx, w_data, blob.data, blob.length, "winreg buffer mismatch"); + } + + torture_comment(tctx, "Testing SetValue (values) succeeded\n"); + + return true; +} + +typedef NTSTATUS (*winreg_open_fn)(struct dcerpc_binding_handle *, TALLOC_CTX *, void *); + +static bool test_SetValue_extended(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + const char *value_name = TEST_VALUE; + enum winreg_Type types[] = { + REG_NONE, + REG_SZ, + REG_EXPAND_SZ, + REG_BINARY, + REG_DWORD, + REG_DWORD_BIG_ENDIAN, + REG_LINK, + REG_MULTI_SZ, + REG_RESOURCE_LIST, + REG_FULL_RESOURCE_DESCRIPTOR, + REG_RESOURCE_REQUIREMENTS_LIST, + REG_QWORD, + 12, + 13, + 14, + 55, + 123456, + 653210, + __LINE__ + }; + int t, l; + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "skipping extended SetValue test against Samba4"); + } + + torture_comment(tctx, "Testing SetValue (extended formats)\n"); + + for (t=0; t < ARRAY_SIZE(types); t++) { + for (l=0; l < 16; l++) { + + enum winreg_Type w_type; + uint32_t w_size, w_length; + uint8_t *w_data; + + uint32_t size; + uint8_t *data; + + size = l; + data = talloc_array(tctx, uint8_t, size); + + generate_random_buffer(data, size); + + torture_assert(tctx, + test_SetValue(b, tctx, handle, value_name, types[t], data, size), + "test_SetValue failed"); + + torture_assert(tctx, + test_winreg_QueryValue(tctx, b, handle, value_name, &w_type, &w_size, &w_length, &w_data), + "test_winreg_QueryValue failed"); + + torture_assert(tctx, + test_DeleteValue(b, tctx, handle, value_name), + "test_DeleteValue failed"); + + torture_assert_int_equal(tctx, w_type, types[t], "winreg type mismatch"); + torture_assert_int_equal(tctx, w_size, size, "winreg size mismatch"); + torture_assert_int_equal(tctx, w_length, size, "winreg length mismatch"); + torture_assert_mem_equal(tctx, w_data, data, size, "winreg buffer mismatch"); + } + } + + torture_comment(tctx, "Testing SetValue (extended formats) succeeded\n"); + + return true; +} + +static bool test_create_keynames(struct dcerpc_binding_handle *b, + struct torture_context *tctx, + struct policy_handle *handle) +{ + const char *keys[] = { + "torture_key", + "torture key", + "torture,key", + "torture/key", + "torture\\key", + }; + int i; + + for (i=0; i < ARRAY_SIZE(keys); i++) { + + enum winreg_CreateAction action_taken; + struct policy_handle new_handle; + char *q, *tmp; + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, handle, keys[i], NULL, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_OK, + &action_taken, + &new_handle), + talloc_asprintf(tctx, "failed to create '%s' key", keys[i])); + + torture_assert_int_equal(tctx, action_taken, REG_CREATED_NEW_KEY, "unexpected action"); + + torture_assert(tctx, + test_DeleteKey_opts(b, tctx, handle, keys[i], WERR_OK), + "failed to delete key"); + + torture_assert(tctx, + test_DeleteKey_opts(b, tctx, handle, keys[i], WERR_FILE_NOT_FOUND), + "failed 2nd delete key"); + + tmp = talloc_strdup(tctx, keys[i]); + + q = strchr(tmp, '\\'); + if (q != NULL) { + *q = '\0'; + q++; + + torture_assert(tctx, + test_DeleteKey_opts(b, tctx, handle, tmp, WERR_OK), + "failed to delete key"); + + torture_assert(tctx, + test_DeleteKey_opts(b, tctx, handle, tmp, WERR_FILE_NOT_FOUND), + "failed 2nd delete key"); + } + } + + return true; +} + +#define KEY_CURRENT_VERSION "SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION" +#define VALUE_CURRENT_VERSION "CurrentVersion" +#define VALUE_SYSTEM_ROOT "SystemRoot" + +static const struct { + const char *values[3]; + uint32_t num_values; + bool existing_value; + const char *error_message; +} multiple_values_tests[] = { + { + .values = { VALUE_CURRENT_VERSION, NULL, NULL }, + .num_values = 1, + .existing_value = true, + .error_message = NULL + },{ + .values = { VALUE_SYSTEM_ROOT, NULL, NULL }, + .num_values = 1, + .existing_value = true, + .error_message = NULL + },{ + .values = { VALUE_CURRENT_VERSION, VALUE_SYSTEM_ROOT, NULL }, + .num_values = 2, + .existing_value = true, + .error_message = NULL + },{ + .values = { VALUE_CURRENT_VERSION, VALUE_SYSTEM_ROOT, + VALUE_CURRENT_VERSION }, + .num_values = 3, + .existing_value = true, + .error_message = NULL + },{ + .values = { VALUE_CURRENT_VERSION, NULL, VALUE_SYSTEM_ROOT }, + .num_values = 3, + .existing_value = false, + .error_message = NULL + },{ + .values = { VALUE_CURRENT_VERSION, "", VALUE_SYSTEM_ROOT }, + .num_values = 3, + .existing_value = false, + .error_message = NULL + },{ + .values = { "IDoNotExist", NULL, NULL }, + .num_values = 1, + .existing_value = false, + .error_message = NULL + },{ + .values = { "IDoNotExist", VALUE_CURRENT_VERSION, NULL }, + .num_values = 2, + .existing_value = false, + .error_message = NULL + },{ + .values = { VALUE_CURRENT_VERSION, "IDoNotExist", NULL }, + .num_values = 2, + .existing_value = false, + .error_message = NULL + } +}; + +static bool test_HKLM_wellknown(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle) +{ + struct policy_handle newhandle; + int i; + + /* FIXME: s3 does not support SEC_FLAG_MAXIMUM_ALLOWED yet */ + if (torture_setting_bool(tctx, "samba3", false)) { + torture_assert(tctx, test_OpenKey_opts(tctx, b, handle, + KEY_CURRENT_VERSION, + REG_OPTION_NON_VOLATILE, + KEY_QUERY_VALUE, + &newhandle, + WERR_OK), + "failed to open current version key"); + } else { + torture_assert(tctx, test_OpenKey(b, tctx, handle, KEY_CURRENT_VERSION, &newhandle), + "failed to open current version key"); + } + + torture_assert(tctx, test_QueryValue_full(b, tctx, &newhandle, VALUE_CURRENT_VERSION, true), + "failed to query current version"); + torture_assert(tctx, test_QueryValue_full(b, tctx, &newhandle, "IDoNotExist", false), + "succeeded to query nonexistent value"); + torture_assert(tctx, test_QueryValue_full(b, tctx, &newhandle, NULL, false), + "succeeded to query value with NULL name"); + torture_assert(tctx, test_QueryValue_full(b, tctx, &newhandle, "", false), + "succeeded to query nonexistent default value (\"\")"); + + if (torture_setting_bool(tctx, "samba4", false)) { + torture_comment(tctx, "skipping QueryMultipleValues{2} tests against Samba4\n"); + goto close_key; + } + + for (i=0; i < ARRAY_SIZE(multiple_values_tests); i++) { + const char *msg; + msg = talloc_asprintf(tctx, + "failed to query %d %sexisting values\n", + multiple_values_tests[i].num_values, + multiple_values_tests[i].existing_value ? "":"non"); + + torture_assert(tctx, + test_QueryMultipleValues_full(b, tctx, &newhandle, + multiple_values_tests[i].num_values, + multiple_values_tests[i].values, + multiple_values_tests[i].existing_value), + msg); + torture_assert(tctx, + test_QueryMultipleValues2_full(b, tctx, &newhandle, + multiple_values_tests[i].num_values, + multiple_values_tests[i].values, + multiple_values_tests[i].existing_value), + msg); + } + + close_key: + torture_assert(tctx, test_CloseKey(b, tctx, &newhandle), + "failed to close current version key"); + + return true; +} + +static bool test_OpenHive(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + int hkey) +{ + struct winreg_OpenHKLM r; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = handle; + + switch (hkey) { + case HKEY_LOCAL_MACHINE: + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenHKLM_r(b, tctx, &r), + "failed to open HKLM"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to open HKLM"); + break; + case HKEY_CURRENT_USER: + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenHKCU_r(b, tctx, (struct winreg_OpenHKCU *)(void *)&r), + "failed to open HKCU"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to open HKCU"); + break; + case HKEY_USERS: + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenHKU_r(b, tctx, (struct winreg_OpenHKU *)(void *)&r), + "failed to open HKU"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to open HKU"); + break; + case HKEY_CLASSES_ROOT: + torture_assert_ntstatus_ok(tctx, + dcerpc_winreg_OpenHKCR_r(b, tctx, (struct winreg_OpenHKCR *)(void *)&r), + "failed to open HKCR"); + torture_assert_werr_ok(tctx, r.out.result, + "failed to open HKCR"); + break; + default: + torture_warning(tctx, "unsupported hkey: 0x%08x\n", hkey); + return false; + } + + return true; +} + +static bool test_volatile_keys(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + int hkey) +{ + struct policy_handle new_handle, hive_handle; + enum winreg_CreateAction action_taken = REG_ACTION_NONE; + + ZERO_STRUCT(new_handle); + ZERO_STRUCT(hive_handle); + + torture_comment(tctx, "Testing VOLATILE key\n"); + + test_DeleteKey(b, tctx, handle, TEST_KEY_VOLATILE); + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, handle, TEST_KEY_VOLATILE, NULL, + REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_OK, + &action_taken, + &new_handle), + "failed to create REG_OPTION_VOLATILE type key"); + + torture_assert_int_equal(tctx, action_taken, REG_CREATED_NEW_KEY, "unexpected action"); + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, &new_handle, TEST_SUBKEY_VOLATILE, NULL, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_CHILD_MUST_BE_VOLATILE, + NULL, + NULL), + "failed to fail create REG_OPTION_VOLATILE type key"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, TEST_KEY_VOLATILE, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_OK), + "failed to open volatile key"); + + torture_assert(tctx, + test_DeleteKey(b, tctx, handle, TEST_KEY_VOLATILE), + "failed to delete key"); + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, handle, TEST_KEY_VOLATILE, NULL, + REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_OK, + &action_taken, + &new_handle), + "failed to create REG_OPTION_VOLATILE type key"); + + torture_assert_int_equal(tctx, action_taken, REG_CREATED_NEW_KEY, "unexpected action"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, TEST_KEY_VOLATILE, + REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_OK), + "failed to open volatile key"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + torture_assert(tctx, + test_OpenHive(tctx, b, &hive_handle, hkey), + "failed top open hive"); + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, &hive_handle, TEST_KEY_VOLATILE, + REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_FILE_NOT_FOUND), + "failed to open volatile key"); + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, &hive_handle, TEST_KEY_VOLATILE, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_FILE_NOT_FOUND), + "failed to open volatile key"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &hive_handle), + "failed to close"); + + torture_assert(tctx, + test_DeleteKey(b, tctx, handle, TEST_KEY_VOLATILE), + "failed to delete key"); + + + torture_comment(tctx, "Testing VOLATILE key succeeded\n"); + + return true; +} + +static const char *kernel_mode_registry_path(struct torture_context *tctx, + int hkey, + const char *sid_string, + const char *path) +{ + switch (hkey) { + case HKEY_LOCAL_MACHINE: + return talloc_asprintf(tctx, "\\Registry\\MACHINE\\%s", path); + case HKEY_CURRENT_USER: + return talloc_asprintf(tctx, "\\Registry\\USER\\%s\\%s", sid_string, path); + case HKEY_USERS: + return talloc_asprintf(tctx, "\\Registry\\USER\\%s", path); + case HKEY_CLASSES_ROOT: + return talloc_asprintf(tctx, "\\Registry\\MACHINE\\Software\\Classes\\%s", path); + default: + torture_warning(tctx, "unsupported hkey: 0x%08x\n", hkey); + return NULL; + } +} + +static bool test_symlink_keys(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key, + int hkey) +{ + struct policy_handle new_handle; + enum winreg_CreateAction action_taken; + DATA_BLOB blob; + uint32_t value = 42; + const char *test_key_symlink_dest; + const char *test_key_symlink; + const char *kernel_mode_path; + + /* disable until we know how to delete a symbolic link */ + torture_skip(tctx, "symlink test disabled"); + + torture_comment(tctx, "Testing REG_OPTION_CREATE_LINK key\n"); + + /* create destination key with testvalue */ + test_key_symlink = talloc_asprintf(tctx, "%s\\%s", + key, TEST_KEY_SYMLINK); + test_key_symlink_dest = talloc_asprintf(tctx, "%s\\%s", + key, TEST_KEY_SYMLINK_DEST); + + test_DeleteKey(b, tctx, handle, test_key_symlink); + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, handle, test_key_symlink_dest, NULL, + 0, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_OK, + &action_taken, + &new_handle), + "failed to create symlink destination"); + + blob = data_blob_talloc_zero(tctx, 4); + SIVAL(blob.data, 0, value); + + torture_assert(tctx, + test_SetValue(b, tctx, &new_handle, "TestValue", REG_DWORD, blob.data, blob.length), + "failed to create TestValue"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + /* create symlink */ + + torture_assert(tctx, + test_CreateKey_opts(tctx, b, handle, test_key_symlink, NULL, + REG_OPTION_CREATE_LINK | REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + NULL, + WERR_OK, + &action_taken, + &new_handle), + "failed to create REG_OPTION_CREATE_LINK type key"); + + torture_assert_int_equal(tctx, action_taken, REG_CREATED_NEW_KEY, "unexpected action"); + + kernel_mode_path = kernel_mode_registry_path(tctx, hkey, NULL, test_key_symlink_dest); + + torture_assert(tctx, + convert_string_talloc(tctx, CH_UNIX, CH_UTF16, + kernel_mode_path, + strlen(kernel_mode_path), /* not NULL terminated */ + &blob.data, &blob.length), + "failed to convert"); + + torture_assert(tctx, + test_SetValue(b, tctx, &new_handle, "SymbolicLinkValue", REG_LINK, blob.data, blob.length), + "failed to create SymbolicLinkValue value"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + /* test follow symlink */ + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, test_key_symlink, + 0, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_OK), + "failed to follow symlink key"); + + torture_assert(tctx, + test_QueryValue(b, tctx, &new_handle, "TestValue"), + "failed to query value"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + /* delete link */ + + torture_assert(tctx, + test_OpenKey_opts(tctx, b, handle, test_key_symlink, + REG_OPTION_OPEN_LINK | REG_OPTION_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &new_handle, + WERR_OK), + "failed to open symlink key"); + + torture_assert(tctx, + test_DeleteValue(b, tctx, &new_handle, "SymbolicLinkValue"), + "failed to delete value SymbolicLinkValue"); + + torture_assert(tctx, + test_CloseKey(b, tctx, &new_handle), + "failed to close"); + + torture_assert(tctx, + test_DeleteKey(b, tctx, handle, test_key_symlink), + "failed to delete key"); + + /* delete destination */ + + torture_assert(tctx, + test_DeleteKey(b, tctx, handle, test_key_symlink_dest), + "failed to delete key"); + + return true; +} + +static bool test_CreateKey_keytypes(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *key, + int hkey) +{ + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "skipping CreateKey keytypes test against Samba"); + } + + torture_assert(tctx, + test_volatile_keys(tctx, b, handle, hkey), + "failed to test volatile keys"); + + torture_assert(tctx, + test_symlink_keys(tctx, b, handle, key, hkey), + "failed to test symlink keys"); + + return true; +} + +static bool test_key_base(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + struct policy_handle *handle, + const char *base_key, + int hkey) +{ + struct policy_handle newhandle; + bool ret = true, created = false, deleted = false; + bool created3 = false; + const char *test_key1; + const char *test_key3; + const char *test_subkey; + + test_Cleanup(b, tctx, handle, base_key); + + if (!test_CreateKey(b, tctx, handle, base_key, NULL)) { + torture_comment(tctx, + "CreateKey(%s) failed\n", base_key); + } + + test_key1 = talloc_asprintf(tctx, "%s\\%s", base_key, TEST_KEY1); + + if (!test_CreateKey(b, tctx, handle, test_key1, NULL)) { + torture_comment(tctx, + "CreateKey failed - not considering a failure\n"); + } else { + created = true; + } + + if (created) { + if (!test_FlushKey(b, tctx, handle)) { + torture_comment(tctx, "FlushKey failed\n"); + ret = false; + } + + if (!test_OpenKey(b, tctx, handle, test_key1, &newhandle)) { + torture_fail(tctx, + "CreateKey failed (OpenKey after Create didn't work)\n"); + } + + if (hkey == HKEY_CURRENT_USER) { + torture_assert(tctx, test_SetValue_simple(b, tctx, &newhandle), + "simple SetValue test failed"); + torture_assert(tctx, test_SetValue_values(b, tctx, &newhandle), + "values SetValue test failed"); + torture_assert(tctx, test_SetValue_extended(b, tctx, &newhandle), + "extended SetValue test failed"); + torture_assert(tctx, test_create_keynames(b, tctx, &newhandle), + "keyname CreateKey test failed"); + } else { + torture_assert(tctx, test_CreateKey_keytypes(tctx, b, &newhandle, test_key1, hkey), + "keytype test failed"); + } + + if (!test_CloseKey(b, tctx, &newhandle)) { + torture_fail(tctx, + "CreateKey failed (CloseKey after Open didn't work)\n"); + } + + if (!test_DeleteKey(b, tctx, handle, test_key1)) { + torture_comment(tctx, "DeleteKey(%s) failed\n", + test_key1); + ret = false; + } else { + deleted = true; + } + + if (!test_FlushKey(b, tctx, handle)) { + torture_comment(tctx, "FlushKey failed\n"); + ret = false; + } + + if (deleted) { + if (!test_OpenKey_opts(tctx, b, handle, test_key1, + REG_OPTION_NON_VOLATILE, + SEC_FLAG_MAXIMUM_ALLOWED, + &newhandle, + WERR_FILE_NOT_FOUND)) { + torture_comment(tctx, + "DeleteKey failed (OpenKey after Delete " + "did not return WERR_FILE_NOT_FOUND)\n"); + ret = false; + } + } + + test_key3 = talloc_asprintf(tctx, "%s\\%s", base_key, TEST_KEY3); + + if (test_CreateKey(b, tctx, handle, test_key3, NULL)) { + created3 = true; + } + + test_subkey = talloc_asprintf(tctx, "%s\\%s", test_key3, TEST_SUBKEY); + + if (created3) { + if (test_CreateKey(b, tctx, handle, test_subkey, NULL)) { + if (!test_DeleteKey(b, tctx, handle, test_subkey)) { + torture_comment(tctx, "DeleteKey(%s) failed\n", test_subkey); + ret = false; + } + } + + if (!test_DeleteKey(b, tctx, handle, test_key3)) { + torture_comment(tctx, "DeleteKey(%s) failed\n", test_key3); + ret = false; + } + } + } + + test_Cleanup(b, tctx, handle, base_key); + + return ret; +} + +static bool test_key_base_sd(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *handle, + const char *base_key) +{ + struct policy_handle newhandle; + bool ret = true, created2 = false, created4 = false; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *test_key2; + const char *test_key4; + + torture_skip(tctx, "security descriptor test disabled\n"); + + if (torture_setting_bool(tctx, "samba3", false) || + torture_setting_bool(tctx, "samba4", false)) { + torture_skip(tctx, "skipping security descriptor tests against Samba"); + } + + test_Cleanup(b, tctx, handle, base_key); + + if (!test_CreateKey(b, tctx, handle, base_key, NULL)) { + torture_comment(tctx, + "CreateKey(%s) failed\n", base_key); + } + + test_key2 = talloc_asprintf(tctx, "%s\\%s", base_key, TEST_KEY2); + + if (test_CreateKey_sd(b, tctx, handle, test_key2, + NULL, &newhandle)) { + created2 = true; + } + + if (created2 && !test_CloseKey(b, tctx, &newhandle)) { + torture_comment(tctx, "CloseKey failed\n"); + ret = false; + } + + test_key4 = talloc_asprintf(tctx, "%s\\%s", base_key, TEST_KEY4); + + if (test_CreateKey_sd(b, tctx, handle, test_key4, NULL, &newhandle)) { + created4 = true; + } + + if (created4 && !test_CloseKey(b, tctx, &newhandle)) { + torture_comment(tctx, "CloseKey failed\n"); + ret = false; + } + + if (created4 && !test_SecurityDescriptors(p, tctx, handle, test_key4)) { + ret = false; + } + + if (created4 && !test_DeleteKey(b, tctx, handle, test_key4)) { + torture_comment(tctx, "DeleteKey(%s) failed\n", test_key4); + ret = false; + } + + if (created2 && !test_DeleteKey(b, tctx, handle, test_key4)) { + torture_comment(tctx, "DeleteKey(%s) failed\n", test_key4); + ret = false; + } + + test_Cleanup(b, tctx, handle, base_key); + + return ret; +} + +static bool test_Open(struct torture_context *tctx, struct dcerpc_pipe *p, + void *userdata) +{ + struct policy_handle handle; + bool ret = true; + struct winreg_OpenHKLM r; + struct dcerpc_binding_handle *b = p->binding_handle; + const char *torture_base_key; + int hkey = 0; + + winreg_open_fn open_fn = (winreg_open_fn)userdata; + + r.in.system_name = 0; + r.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + r.out.handle = &handle; + + torture_assert_ntstatus_ok(tctx, open_fn(b, tctx, &r), + "open"); + + if (!test_GetVersion(b, tctx, &handle)) { + torture_comment(tctx, "GetVersion failed\n"); + ret = false; + } + + if (open_fn == (winreg_open_fn)dcerpc_winreg_OpenHKLM_r) { + hkey = HKEY_LOCAL_MACHINE; + torture_base_key = "SOFTWARE\\Samba\\" TEST_KEY_BASE; + } else if (open_fn == (winreg_open_fn)dcerpc_winreg_OpenHKU_r) { + hkey = HKEY_USERS; + torture_base_key = TEST_KEY_BASE; + } else if (open_fn == (winreg_open_fn)dcerpc_winreg_OpenHKCR_r) { + hkey = HKEY_CLASSES_ROOT; + torture_base_key = TEST_KEY_BASE; + } else if (open_fn == (winreg_open_fn)dcerpc_winreg_OpenHKCU_r) { + hkey = HKEY_CURRENT_USER; + torture_base_key = TEST_KEY_BASE; + } else { + torture_fail(tctx, "unsupported hkey"); + } + + if (hkey == HKEY_LOCAL_MACHINE) { + torture_assert(tctx, + test_HKLM_wellknown(tctx, b, &handle), + "failed to test HKLM wellknown keys"); + } + + if (!test_key_base(tctx, b, &handle, torture_base_key, hkey)) { + torture_warning(tctx, "failed to test TEST_KEY_BASE(%s)", + torture_base_key); + ret = false; + } + + if (!test_key_base_sd(tctx, p, &handle, torture_base_key)) { + torture_warning(tctx, "failed to test TEST_KEY_BASE(%s) sd", + torture_base_key); + ret = false; + } + + /* The HKCR hive has a very large fanout */ + if (hkey == HKEY_CLASSES_ROOT) { + if(!test_key(p, tctx, &handle, MAX_DEPTH - 1, false)) { + ret = false; + } + } else if (hkey == HKEY_LOCAL_MACHINE) { + /* FIXME we are not allowed to enum values in the HKLM root */ + } else { + if (!test_key(p, tctx, &handle, 0, false)) { + ret = false; + } + } + + return ret; +} + +struct torture_suite *torture_rpc_winreg(TALLOC_CTX *mem_ctx) +{ + struct torture_rpc_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "winreg"); + struct torture_test *test; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "winreg", + &ndr_table_winreg); + + test = torture_rpc_tcase_add_test(tcase, "InitiateSystemShutdown", + test_InitiateSystemShutdown); + test->dangerous = true; + + test = torture_rpc_tcase_add_test(tcase, "InitiateSystemShutdownEx", + test_InitiateSystemShutdownEx); + test->dangerous = true; + + torture_rpc_tcase_add_test_ex(tcase, "HKLM", + test_Open, + (void *)dcerpc_winreg_OpenHKLM_r); + torture_rpc_tcase_add_test_ex(tcase, "HKU", + test_Open, + (void *)dcerpc_winreg_OpenHKU_r); + torture_rpc_tcase_add_test_ex(tcase, "HKCR", + test_Open, + (void *)dcerpc_winreg_OpenHKCR_r); + torture_rpc_tcase_add_test_ex(tcase, "HKCU", + test_Open, + (void *)dcerpc_winreg_OpenHKCU_r); + + return suite; +} diff --git a/source4/torture/rpc/witness.c b/source4/torture/rpc/witness.c new file mode 100644 index 0000000..602e8ca --- /dev/null +++ b/source4/torture/rpc/witness.c @@ -0,0 +1,911 @@ +/* + Unix SMB/CIFS implementation. + test suite for rpc witness operations + + Copyright (C) Guenther Deschner 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "torture/rpc/torture_rpc.h" +#include "librpc/gen_ndr/ndr_witness_c.h" +#include "librpc/gen_ndr/ndr_srvsvc_c.h" +#include "librpc/gen_ndr/ndr_clusapi_c.h" +#include "param/param.h" +#include <tevent.h> +#include "lib/cmdline/cmdline.h" + +struct torture_test_clusapi_state { + struct dcerpc_pipe *p; +}; + +struct torture_test_witness_state { + const char *net_name; + const char *share_name; + struct witness_interfaceList *list; + struct policy_handle context_handle; + struct torture_test_clusapi_state clusapi; +}; + +static bool test_witness_GetInterfaceList(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_GetInterfaceList r; + struct witness_interfaceList *l; + struct torture_test_witness_state *state = + (struct torture_test_witness_state *)data; + + r.out.interface_list = &l; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_GetInterfaceList_r(b, tctx, &r), + "GetInterfaceList failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "GetInterfaceList failed"); + + state->list = l; + + return true; +} + +static bool find_sofs_share(struct torture_context *tctx, + const char **sofs_sharename) +{ + struct dcerpc_pipe *p; + struct dcerpc_binding_handle *b; + struct srvsvc_NetShareEnumAll r; + struct srvsvc_NetShareInfoCtr info_ctr; + struct srvsvc_NetShareCtr1 ctr1; + uint32_t resume_handle = 0; + uint32_t totalentries = 0; + int i; + + torture_assert_ntstatus_ok(tctx, + torture_rpc_connection_transport(tctx, &p, &ndr_table_srvsvc, + NCACN_NP, 0, 0), + "failed to setup srvsvc connection"); + + b = p->binding_handle; + + ZERO_STRUCT(ctr1); + + info_ctr.level = 1; + info_ctr.ctr.ctr1 = &ctr1; + + r.in.server_unc = dcerpc_server_name(p); + r.in.max_buffer = -1; + r.in.info_ctr = &info_ctr; + r.in.resume_handle = &resume_handle; + r.out.totalentries = &totalentries; + r.out.info_ctr = &info_ctr; + r.out.resume_handle = &resume_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_srvsvc_NetShareEnumAll_r(b, tctx, &r), + "failed to call srvsvc_NetShareEnumAll"); + + torture_assert_werr_ok(tctx, + r.out.result, + "failed to call srvsvc_NetShareEnumAll"); + + for (i=0; i < r.out.info_ctr->ctr.ctr1->count; i++) { + + if (r.out.info_ctr->ctr.ctr1->array[i].type == STYPE_CLUSTER_SOFS) { + *sofs_sharename = talloc_strdup(tctx, r.out.info_ctr->ctr.ctr1->array[i].name); + if (*sofs_sharename == NULL) { + return false; + } + torture_comment(tctx, "using SOFS share: %s\n", *sofs_sharename); + return true; + } + if (r.out.info_ctr->ctr.ctr1->array[i].type == STYPE_DISKTREE) { + *sofs_sharename = talloc_strdup(tctx, r.out.info_ctr->ctr.ctr1->array[i].name); + if (*sofs_sharename == NULL) { + return false; + } + torture_comment(tctx, "assuming SOFS share: %s\n", *sofs_sharename); + return true; + } + } + + return false; +} + +static bool init_witness_test_state(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct torture_test_witness_state *state) +{ + if (state->net_name == NULL) { + state->net_name = lpcfg_parm_string(tctx->lp_ctx, NULL, "torture", "net_name"); + } + + if (state->list == NULL) { + torture_assert(tctx, + test_witness_GetInterfaceList(tctx, p, state), + "failed to retrieve GetInterfaceList"); + } + + if (state->share_name == NULL) { + find_sofs_share(tctx, &state->share_name); + } + + return true; +} + +static bool test_witness_UnRegister_with_handle(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *context_handle) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_UnRegister r; + + r.in.context_handle = *context_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_UnRegister_r(b, tctx, &r), + "UnRegister failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "UnRegister failed"); + + /* make sure we are not able/allowed to reuse context handles after they + * have been unregistered */ + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_UnRegister_r(b, tctx, &r), + "UnRegister failed"); + + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_PARAMETER, + "UnRegister failed"); + + return true; +} + +static bool test_witness_UnRegister(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + /* acquire handle and free afterwards */ + return true; +} + +static bool get_ip_address_from_interface(struct torture_context *tctx, + struct witness_interfaceInfo *i, + const char **ip_address) +{ + if (i->flags & WITNESS_INFO_IPv4_VALID) { + *ip_address = talloc_strdup(tctx, i->ipv4); + torture_assert(tctx, *ip_address, "talloc_strdup failed"); + return true; + } + + if (i->flags & WITNESS_INFO_IPv6_VALID) { + *ip_address = talloc_strdup(tctx, i->ipv6); + torture_assert(tctx, *ip_address, "talloc_strdup failed"); + return true; + } + + return false; +} + +static bool check_valid_interface(struct torture_context *tctx, + struct witness_interfaceInfo *i) +{ + /* continue looking for an interface that allows witness + * registration */ + if (!(i->flags & WITNESS_INFO_WITNESS_IF)) { + return false; + } + + /* witness should be available of course */ + if (i->state != WITNESS_STATE_AVAILABLE) { + return false; + } + + return true; +} + +static bool test_witness_Register(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_Register r; + struct policy_handle context_handle; + struct torture_test_witness_state *state = + (struct torture_test_witness_state *)data; + int i; + + struct { + enum witness_version version; + const char *net_name; + const char *ip_address; + const char *client_computer_name; + NTSTATUS expected_status; + WERROR expected_result; + } tests[] = { + { + .version = 0, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = 1, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = 123456, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = -1, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V2, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V1, + .net_name = "", + .ip_address = "", + .client_computer_name = "", + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + },{ + .version = WITNESS_V1, + .net_name = NULL, + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + },{ + .version = WITNESS_V2, + .net_name = NULL, + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V1, + .net_name = dcerpc_server_name(p), + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + } + + }; + + for (i=0; i < ARRAY_SIZE(tests); i++) { + + ZERO_STRUCT(r); + + r.out.context_handle = &context_handle; + + r.in.version = tests[i].version; + r.in.net_name = tests[i].net_name; + r.in.ip_address = tests[i].ip_address; + r.in.client_computer_name = tests[i].client_computer_name; + + torture_assert_ntstatus_equal(tctx, + dcerpc_witness_Register_r(b, tctx, &r), + tests[i].expected_status, + "Register failed"); + + torture_assert_werr_equal(tctx, + r.out.result, + tests[i].expected_result, + "Register failed"); + + if (W_ERROR_IS_OK(r.out.result)) { + + /* we have a handle, make sure to unregister it */ + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, r.out.context_handle), + "Failed to unregister"); + } + } + + init_witness_test_state(tctx, p, state); + + for (i=0; state->list && i < state->list->num_interfaces; i++) { + + const char *ip_address; + struct witness_interfaceInfo interface = state->list->interfaces[i]; + + if (!check_valid_interface(tctx, &interface)) { + continue; + } + + torture_assert(tctx, + get_ip_address_from_interface(tctx, &interface, &ip_address), + "failed to get ip_address from interface"); + + r.in.version = WITNESS_V1; + r.in.net_name = state->net_name; + r.in.ip_address = ip_address; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_Register_r(b, tctx, &r), + "Register failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "Register failed"); + + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, r.out.context_handle), + "Failed to unregister"); + } + + return true; +} + +static bool test_witness_RegisterEx(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_RegisterEx r; + struct policy_handle context_handle; + struct torture_test_witness_state *state = + (struct torture_test_witness_state *)data; + int i; + + struct { + enum witness_version version; + const char *net_name; + const char *ip_address; + const char *client_computer_name; + NTSTATUS expected_status; + WERROR expected_result; + } tests[] = { + { + .version = 0, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = 1, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = 123456, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = -1, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V1, + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V2, + .net_name = "", + .ip_address = "", + .client_computer_name = "", + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + },{ + .version = WITNESS_V2, + .net_name = NULL, + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + },{ + .version = WITNESS_V1, + .net_name = NULL, + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_REVISION_MISMATCH + },{ + .version = WITNESS_V2, + .net_name = dcerpc_server_name(p), + .ip_address = NULL, + .client_computer_name = lpcfg_netbios_name(tctx->lp_ctx), + .expected_status = NT_STATUS_OK, + .expected_result = WERR_INVALID_PARAMETER + } + + }; + + for (i=0; i < ARRAY_SIZE(tests); i++) { + + ZERO_STRUCT(r); + + r.out.context_handle = &context_handle; + + r.in.version = tests[i].version; + r.in.net_name = tests[i].net_name; + r.in.ip_address = tests[i].ip_address; + r.in.client_computer_name = tests[i].client_computer_name; + + torture_assert_ntstatus_equal(tctx, + dcerpc_witness_RegisterEx_r(b, tctx, &r), + tests[i].expected_status, + "RegisterEx failed"); + + torture_assert_werr_equal(tctx, + r.out.result, + tests[i].expected_result, + "RegisterEx failed"); + + if (W_ERROR_IS_OK(r.out.result)) { + + /* we have a handle, make sure to unregister it */ + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, r.out.context_handle), + "Failed to unregister"); + } + } + + init_witness_test_state(tctx, p, state); + + for (i=0; state->list && i < state->list->num_interfaces; i++) { + + const char *ip_address; + struct witness_interfaceInfo interface = state->list->interfaces[i]; + + if (!check_valid_interface(tctx, &interface)) { + continue; + } + + torture_assert(tctx, + get_ip_address_from_interface(tctx, &interface, &ip_address), + "failed to get ip_address from interface"); + + r.in.version = WITNESS_V2; + r.in.net_name = state->net_name; + r.in.ip_address = ip_address; + + /* + * a valid request with an invalid sharename fails with + * WERR_INVALID_STATE + */ + r.in.share_name = "any_invalid_share_name"; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_RegisterEx_r(b, tctx, &r), + "RegisterEx failed"); + + torture_assert_werr_equal(tctx, + r.out.result, + WERR_INVALID_STATE, + "RegisterEx failed"); + + r.in.share_name = NULL; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_RegisterEx_r(b, tctx, &r), + "RegisterEx failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "RegisterEx failed"); + + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, r.out.context_handle), + "Failed to unregister"); + } + + return true; +} + +static bool setup_clusapi_connection(struct torture_context *tctx, + struct torture_test_witness_state *s) +{ + struct dcerpc_binding *binding; + + if (s->clusapi.p) { + return true; + } + + torture_assert_ntstatus_ok(tctx, + torture_rpc_binding(tctx, &binding), + "failed to retrieve torture binding"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_binding_set_transport(binding, NCACN_IP_TCP), + "failed to set transport"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_binding_set_flags(binding, DCERPC_SEAL, 0), + "failed to set dcerpc flags"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_pipe_connect_b(tctx, &s->clusapi.p, binding, + &ndr_table_clusapi, + samba_cmdline_get_creds(), + tctx->ev, tctx->lp_ctx), + "failed to connect dcerpc pipe"); + + return true; +} + +#if 0 +static bool cluster_get_nodes(struct torture_context *tctx, + struct torture_test_witness_state *s) +{ + struct clusapi_CreateEnum r; + struct ENUM_LIST *ReturnEnum; + WERROR rpc_status; + struct dcerpc_binding_handle *b; + + torture_assert(tctx, + setup_clusapi_connection(tctx, s), + "failed to setup clusapi connection"); + + b = s->clusapi.p->binding_handle; + + r.in.dwType = CLUSTER_ENUM_NODE; + r.out.ReturnEnum = &ReturnEnum; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_CreateEnum_r(b, tctx, &r), + "failed to enumerate nodes"); + + return true; +} +#endif + +static bool test_GetResourceState_int(struct torture_context *tctx, + struct dcerpc_pipe *p, + struct policy_handle *hResource, + enum clusapi_ClusterResourceState *State) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct clusapi_GetResourceState r; + const char *NodeName; + const char *GroupName; + WERROR rpc_status; + + r.in.hResource = *hResource; + r.out.State = State; + r.out.NodeName = &NodeName; + r.out.GroupName = &GroupName; + r.out.rpc_status = &rpc_status; + + torture_assert_ntstatus_ok(tctx, + dcerpc_clusapi_GetResourceState_r(b, tctx, &r), + "GetResourceState failed"); + torture_assert_werr_ok(tctx, + r.out.result, + "GetResourceState failed"); + + return true; +} + +static bool toggle_cluster_resource_state(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *resource_name, + enum clusapi_ClusterResourceState *old_state, + enum clusapi_ClusterResourceState *new_state) +{ + struct policy_handle hResource; + enum clusapi_ClusterResourceState State; + + torture_assert(tctx, + test_OpenResource_int(tctx, p, resource_name, &hResource), + "failed to open resource"); + torture_assert(tctx, + test_GetResourceState_int(tctx, p, &hResource, &State), + "failed to query resource state"); + + if (old_state) { + *old_state = State; + } + + switch (State) { + case ClusterResourceOffline: + if (!test_OnlineResource_int(tctx, p, &hResource)) { + test_CloseResource_int(tctx, p, &hResource); + torture_warning(tctx, "failed to set resource online"); + return false; + } + break; + case ClusterResourceOnline: + if (!test_OfflineResource_int(tctx, p, &hResource)) { + test_CloseResource_int(tctx, p, &hResource); + torture_warning(tctx, "failed to set resource offline"); + return false; + } + break; + + default: + break; + } + + torture_assert(tctx, + test_GetResourceState_int(tctx, p, &hResource, &State), + "failed to query resource state"); + + if (new_state) { + *new_state = State; + } + + test_CloseResource_int(tctx, p, &hResource); + + return true; +} + +static bool test_witness_AsyncNotify(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_AsyncNotify r; + struct witness_notifyResponse *response; + struct torture_test_witness_state *state = + (struct torture_test_witness_state *)data; + int i; + + init_witness_test_state(tctx, p, state); + + setup_clusapi_connection(tctx, state); + + for (i=0; state->list && i < state->list->num_interfaces; i++) { + + const char *ip_address; + struct witness_interfaceInfo interface = state->list->interfaces[i]; + struct witness_Register reg; + struct tevent_req *req; + enum clusapi_ClusterResourceState old_state, new_state; + + if (!check_valid_interface(tctx, &interface)) { + continue; + } + + torture_assert(tctx, + get_ip_address_from_interface(tctx, &interface, &ip_address), + "failed to get ip_address from interface"); + + reg.in.version = WITNESS_V1; + reg.in.net_name = state->net_name; + reg.in.ip_address = ip_address; + reg.in.client_computer_name = lpcfg_netbios_name(tctx->lp_ctx); + reg.out.context_handle = &state->context_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_Register_r(b, tctx, ®), + "Register failed"); + + torture_assert_werr_ok(tctx, + reg.out.result, + "Register failed"); + + r.in.context_handle = state->context_handle; + r.out.response = &response; + + req = dcerpc_witness_AsyncNotify_r_send(tctx, tctx->ev, b, &r); + torture_assert(tctx, req, "failed to create request"); + + torture_assert(tctx, + toggle_cluster_resource_state(tctx, state->clusapi.p, state->net_name, &old_state, &new_state), + "failed to toggle cluster resource state"); + torture_assert(tctx, old_state != new_state, "failed to change cluster resource state"); + + torture_assert(tctx, + tevent_req_poll(req, tctx->ev), + "failed to call event loop"); + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_AsyncNotify_r_recv(req, tctx), + "failed to receive reply"); + + torture_assert_int_equal(tctx, response->num, 1, "num"); + torture_assert_int_equal(tctx, response->type, WITNESS_NOTIFY_RESOURCE_CHANGE, "type"); + + /* + * TODO: find out how ClusterResourceOfflinePending and + * ClusterResourceOnlinePending are represented as witness + * types. + */ + + if (new_state == ClusterResourceOffline) { + torture_assert_int_equal(tctx, response->messages[0].resource_change.type, WITNESS_RESOURCE_STATE_UNAVAILABLE, "resource_change.type"); + } + if (new_state == ClusterResourceOnline) { + torture_assert_int_equal(tctx, response->messages[0].resource_change.type, WITNESS_RESOURCE_STATE_AVAILABLE, "resource_change.type"); + } + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, &state->context_handle), + "Failed to unregister"); + + ZERO_STRUCT(state->context_handle); + + torture_assert(tctx, + toggle_cluster_resource_state(tctx, state->clusapi.p, state->net_name, &old_state, &new_state), + "failed to toggle cluster resource state"); + torture_assert(tctx, old_state != new_state, "failed to change cluster resource state"); + } + + return true; +} + +static bool test_do_witness_RegisterEx(struct torture_context *tctx, + struct dcerpc_binding_handle *b, + uint32_t version, + const char *net_name, + const char *share_name, + const char *ip_address, + const char *client_computer_name, + uint32_t flags, + uint32_t timeout, + struct policy_handle *context_handle) +{ + struct witness_RegisterEx r; + + r.in.version = version; + r.in.net_name = net_name; + r.in.share_name = NULL; + r.in.ip_address = ip_address; + r.in.client_computer_name = client_computer_name; + r.in.flags = flags; + r.in.timeout = timeout; + r.out.context_handle = context_handle; + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_RegisterEx_r(b, tctx, &r), + "RegisterEx failed"); + + torture_assert_werr_ok(tctx, + r.out.result, + "RegisterEx failed"); + + return true; +} + +static void torture_subunit_report_time(struct torture_context *tctx) +{ + struct timespec tp; + struct tm *tmp; + char timestr[200]; + + if (clock_gettime(CLOCK_REALTIME, &tp) != 0) { + torture_comment(tctx, "failed to call clock_gettime"); + return; + } + + tmp = gmtime(&tp.tv_sec); + if (!tmp) { + torture_comment(tctx, "failed to call gmtime"); + return; + } + + if (strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", tmp) <= 0) { + torture_comment(tctx, "failed to call strftime"); + return; + } + + torture_comment(tctx, "time: %s.%06ld\n", timestr, tp.tv_nsec / 1000); +} + +static bool test_witness_AsyncNotify_timeouts(struct torture_context *tctx, + struct dcerpc_pipe *p, + void *data) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + struct witness_AsyncNotify r; + struct witness_notifyResponse *response; + struct torture_test_witness_state *state = + (struct torture_test_witness_state *)data; + int i; + + init_witness_test_state(tctx, p, state); + + setup_clusapi_connection(tctx, state); + + for (i=0; state->list && i < state->list->num_interfaces; i++) { + + const char *ip_address; + struct witness_interfaceInfo interface = state->list->interfaces[i]; + uint32_t timeouts[] = { + 0, 1, 10, 100, 120 + }; + int t; + uint32_t old_timeout; + + if (!check_valid_interface(tctx, &interface)) { + continue; + } + + torture_assert(tctx, + get_ip_address_from_interface(tctx, &interface, &ip_address), + "failed to get ip_address from interface"); + + for (t=0; t < ARRAY_SIZE(timeouts); t++) { + + torture_comment(tctx, "Testing Async Notify with timeout of %d milliseconds", timeouts[t]); + + torture_assert(tctx, + test_do_witness_RegisterEx(tctx, b, + WITNESS_V2, + state->net_name, + NULL, + ip_address, + lpcfg_netbios_name(tctx->lp_ctx), + 0, + timeouts[t], + &state->context_handle), + "failed to RegisterEx"); + + r.in.context_handle = state->context_handle; + r.out.response = &response; + + old_timeout = dcerpc_binding_handle_set_timeout(b, UINT_MAX); + + torture_subunit_report_time(tctx); + + torture_assert_ntstatus_ok(tctx, + dcerpc_witness_AsyncNotify_r(b, tctx, &r), + "AsyncNotify failed"); + torture_assert_werr_equal(tctx, + r.out.result, + WERR_TIMEOUT, + "AsyncNotify failed"); + + torture_subunit_report_time(tctx); + + dcerpc_binding_handle_set_timeout(b, old_timeout); + + torture_assert(tctx, + test_witness_UnRegister_with_handle(tctx, p, &state->context_handle), + "Failed to unregister"); + + ZERO_STRUCT(state->context_handle); + } + } + + return true; +} + +struct torture_suite *torture_rpc_witness(TALLOC_CTX *mem_ctx) +{ + struct torture_rpc_tcase *tcase; + struct torture_suite *suite = torture_suite_create(mem_ctx, "witness"); + struct torture_test_witness_state *state; + + tcase = torture_suite_add_rpc_iface_tcase(suite, "witness", + &ndr_table_witness); + + state = talloc_zero(tcase, struct torture_test_witness_state); + + torture_rpc_tcase_add_test_ex(tcase, "GetInterfaceList", + test_witness_GetInterfaceList, state); + torture_rpc_tcase_add_test_ex(tcase, "Register", + test_witness_Register, state); + torture_rpc_tcase_add_test_ex(tcase, "UnRegister", + test_witness_UnRegister, state); + torture_rpc_tcase_add_test_ex(tcase, "RegisterEx", + test_witness_RegisterEx, state); + torture_rpc_tcase_add_test_ex(tcase, "AsyncNotify", + test_witness_AsyncNotify, state); + torture_rpc_tcase_add_test_ex(tcase, "AsyncNotify_timeouts", + test_witness_AsyncNotify_timeouts, state); + + return suite; +} diff --git a/source4/torture/rpc/wkssvc.c b/source4/torture/rpc/wkssvc.c new file mode 100644 index 0000000..c43e16f --- /dev/null +++ b/source4/torture/rpc/wkssvc.c @@ -0,0 +1,1458 @@ +/* + Unix SMB/CIFS implementation. + test suite for wkssvc rpc operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Günther Deschner 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 "torture/torture.h" +#include "librpc/gen_ndr/ndr_wkssvc_c.h" +#include "torture/rpc/torture_rpc.h" +#include "lib/cmdline/cmdline.h" +#include "param/param.h" +#include "libcli/auth/libcli_auth.h" + +#define SMBTORTURE_MACHINE_NAME "smbtrt_name" +#define SMBTORTURE_ALTERNATE_NAME "smbtrt_altname" +#define SMBTORTURE_TRANSPORT_NAME "\\Device\\smbtrt_transport_name" +#define SMBTORTURE_USE_NAME "S:" +#define SMBTORTURE_MESSAGE "You are currently tortured by Samba" + +static bool test_NetWkstaGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetWkstaGetInfo r; + union wkssvc_NetWkstaInfo info; + uint16_t levels[] = {100, 101, 102, 502}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.out.info = &info; + + for (i=0;i<ARRAY_SIZE(levels);i++) { + r.in.level = levels[i]; + torture_comment(tctx, "Testing NetWkstaGetInfo level %u\n", + r.in.level); + status = dcerpc_wkssvc_NetWkstaGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + talloc_asprintf(tctx, "NetWkstaGetInfo level %u failed", + r.in.level)); + torture_assert_werr_ok(tctx, r.out.result, + talloc_asprintf(tctx, "NetWkstaGetInfo level %u failed", + r.in.level)); + } + + return true; +} + +static bool test_NetWkstaTransportEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetWkstaTransportEnum r; + uint32_t resume_handle = 0; + struct wkssvc_NetWkstaTransportInfo info; + union wkssvc_NetWkstaTransportCtr ctr; + struct wkssvc_NetWkstaTransportCtr0 ctr0; + uint32_t total_entries = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(ctr0); + ctr.ctr0 = &ctr0; + + info.level = 0; + info.ctr = ctr; + + r.in.server_name = dcerpc_server_name(p); + r.in.info = &info; + r.in.max_buffer = (uint32_t)-1; + r.in.resume_handle = &resume_handle; + r.out.total_entries = &total_entries; + r.out.info = &info; + r.out.resume_handle = &resume_handle; + + torture_comment(tctx, "Testing NetWkstaTransportEnum level 0\n"); + + status = dcerpc_wkssvc_NetWkstaTransportEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetWkstaTransportEnum failed"); + torture_assert_werr_ok(tctx, r.out.result, talloc_asprintf(tctx, + "NetWkstaTransportEnum level %u failed", + info.level)); + + return true; +} + +static bool test_NetrWkstaTransportAdd(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrWkstaTransportAdd r; + struct wkssvc_NetWkstaTransportInfo0 info0; + uint32_t parm_err = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info0); + + info0.quality_of_service = 0xffff; + info0.vc_count = 0; + info0.name = SMBTORTURE_TRANSPORT_NAME; + info0.address = "000000000000"; + info0.wan_link = 0x400; + + r.in.server_name = dcerpc_server_name(p); + r.in.level = 0; + r.in.info0 = &info0; + r.in.parm_err = r.out.parm_err = &parm_err; + + torture_comment(tctx, "Testing NetrWkstaTransportAdd level 0\n"); + + status = dcerpc_wkssvc_NetrWkstaTransportAdd_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrWkstaTransportAdd failed"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_INVALID_PARAMETER, + "NetrWkstaTransportAdd level 0 failed"); + + return true; +} + +static bool test_NetrWkstaTransportDel(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrWkstaTransportDel r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.transport_name = SMBTORTURE_TRANSPORT_NAME; + r.in.unknown3 = 0; + + torture_comment(tctx, "Testing NetrWkstaTransportDel\n"); + + status = dcerpc_wkssvc_NetrWkstaTransportDel_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrWkstaTransportDel failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrWkstaTransportDel"); + + return true; +} + +static bool test_NetWkstaEnumUsers(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetWkstaEnumUsers r; + uint32_t handle = 0; + uint32_t entries_read = 0; + struct wkssvc_NetWkstaEnumUsersInfo info; + struct wkssvc_NetWkstaEnumUsersCtr0 *user0; + struct wkssvc_NetWkstaEnumUsersCtr1 *user1; + uint32_t levels[] = { 0, 1 }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + ZERO_STRUCT(info); + + info.level = levels[i]; + switch (info.level) { + case 0: + user0 = talloc_zero(tctx, + struct wkssvc_NetWkstaEnumUsersCtr0); + info.ctr.user0 = user0; + break; + case 1: + user1 = talloc_zero(tctx, + struct wkssvc_NetWkstaEnumUsersCtr1); + info.ctr.user1 = user1; + break; + default: + break; + } + + r.in.server_name = dcerpc_server_name(p); + r.in.prefmaxlen = (uint32_t)-1; + r.in.info = r.out.info = &info; + r.in.resume_handle = r.out.resume_handle = &handle; + + r.out.entries_read = &entries_read; + + torture_comment(tctx, "Testing NetWkstaEnumUsers level %u\n", + levels[i]); + + status = dcerpc_wkssvc_NetWkstaEnumUsers_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetWkstaEnumUsers failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetWkstaEnumUsers failed"); + } + + return true; +} + +static bool test_NetrWkstaUserGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrWkstaUserGetInfo r; + union wkssvc_NetrWkstaUserInfo info; + const char *dom = lpcfg_workgroup(tctx->lp_ctx); + struct cli_credentials *creds = samba_cmdline_get_creds(); + const char *user = cli_credentials_get_username(creds); + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + const struct { + const char *unknown; + uint32_t level; + WERROR result; + } tests[] = { + { NULL, 0, WERR_NO_SUCH_LOGON_SESSION }, + { NULL, 1, WERR_NO_SUCH_LOGON_SESSION }, + { NULL, 1101, WERR_OK }, + { dom, 0, WERR_INVALID_PARAMETER }, + { dom, 1, WERR_INVALID_PARAMETER }, + { dom, 1101, WERR_INVALID_PARAMETER }, + { user, 0, WERR_INVALID_PARAMETER }, + { user, 1, WERR_INVALID_PARAMETER }, + { user, 1101, WERR_INVALID_PARAMETER }, + }; + + for (i=0; i<ARRAY_SIZE(tests); i++) { + r.in.unknown = tests[i].unknown; + r.in.level = tests[i].level; + r.out.info = &info; + + torture_comment(tctx, "Testing NetrWkstaUserGetInfo level %u\n", + r.in.level); + + status = dcerpc_wkssvc_NetrWkstaUserGetInfo_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrWkstaUserGetInfo failed"); + torture_assert_werr_equal(tctx, r.out.result, + tests[i].result, + "NetrWkstaUserGetInfo failed"); + } + + return true; +} + +static bool test_NetrUseEnum(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUseEnum r; + uint32_t handle = 0; + uint32_t entries_read = 0; + struct wkssvc_NetrUseEnumInfo info; + struct wkssvc_NetrUseEnumCtr0 *use0; + struct wkssvc_NetrUseEnumCtr1 *use1; + struct wkssvc_NetrUseEnumCtr2 *use2; + uint32_t levels[] = { 0, 1, 2 }; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + ZERO_STRUCT(info); + + info.level = levels[i]; + switch (info.level) { + case 0: + use0 = talloc_zero(tctx, struct wkssvc_NetrUseEnumCtr0); + info.ctr.ctr0 = use0; + break; + case 1: + use1 = talloc_zero(tctx, struct wkssvc_NetrUseEnumCtr1); + info.ctr.ctr1 = use1; + break; + case 2: + use2 = talloc_zero(tctx, struct wkssvc_NetrUseEnumCtr2); + info.ctr.ctr2 = use2; + break; + default: + break; + } + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.prefmaxlen = (uint32_t)-1; + r.in.info = r.out.info = &info; + r.in.resume_handle = r.out.resume_handle = &handle; + + r.out.entries_read = &entries_read; + + torture_comment(tctx, "Testing NetrUseEnum level %u\n", + levels[i]); + + status = dcerpc_wkssvc_NetrUseEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUseEnum failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrUseEnum failed"); + } + + return true; +} + +static bool test_NetrUseAdd(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUseAdd r; + struct wkssvc_NetrUseInfo0 info0; + struct wkssvc_NetrUseInfo1 info1; + union wkssvc_NetrUseGetInfoCtr *ctr; + uint32_t parm_err = 0; + struct dcerpc_binding_handle *b = p->binding_handle; + + ctr = talloc(tctx, union wkssvc_NetrUseGetInfoCtr); + + ZERO_STRUCT(info0); + + info0.local = SMBTORTURE_USE_NAME; + info0.remote = "\\\\localhost\\c$"; + + ctr->info0 = &info0; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.level = 0; + r.in.ctr = ctr; + r.in.parm_err = r.out.parm_err = &parm_err; + + torture_comment(tctx, "Testing NetrUseAdd level %u\n", + r.in.level); + + status = dcerpc_wkssvc_NetrUseAdd_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUseAdd failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_INVALID_LEVEL, + "NetrUseAdd failed"); + + ZERO_STRUCT(r); + ZERO_STRUCT(info1); + + info1.local = SMBTORTURE_USE_NAME; + info1.remote = "\\\\localhost\\sysvol"; + info1.password = NULL; + + ctr->info1 = &info1; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.level = 1; + r.in.ctr = ctr; + r.in.parm_err = r.out.parm_err = &parm_err; + + torture_comment(tctx, "Testing NetrUseAdd level %u\n", + r.in.level); + + status = dcerpc_wkssvc_NetrUseAdd_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUseAdd failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrUseAdd failed"); + + return true; +} + +static bool test_NetrUseDel(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUseDel r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.use_name = SMBTORTURE_USE_NAME; + r.in.force_cond = 0; + + torture_comment(tctx, "Testing NetrUseDel\n"); + + status = dcerpc_wkssvc_NetrUseDel_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUseDel failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrUseDel failed"); + return true; +} + +static bool test_NetrUseGetInfo_level(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *use_name, + uint32_t level, + WERROR werr) +{ + NTSTATUS status; + struct wkssvc_NetrUseGetInfo r; + union wkssvc_NetrUseGetInfoCtr ctr; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(ctr); + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.use_name = use_name; + r.in.level = level; + r.out.ctr = &ctr; + status = dcerpc_wkssvc_NetrUseGetInfo_r(b, tctx, &r); + + torture_assert_ntstatus_ok(tctx, status, + "NetrUseGetInfo failed"); + torture_assert_werr_equal(tctx, r.out.result, werr, + "NetrUseGetInfo failed"); + return true; +} + +static bool test_NetrUseGetInfo(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUseEnum r; + uint32_t handle = 0; + uint32_t entries_read = 0; + struct wkssvc_NetrUseEnumInfo info; + struct wkssvc_NetrUseEnumCtr0 *use0; + uint32_t levels[] = { 0, 1, 2 }; + const char *use_name = NULL; + int i, k; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(info); + + info.level = 0; + use0 = talloc_zero(tctx, struct wkssvc_NetrUseEnumCtr0); + info.ctr.ctr0 = use0; + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.prefmaxlen = (uint32_t)-1; + r.in.info = r.out.info = &info; + r.in.resume_handle = r.out.resume_handle = &handle; + r.out.entries_read = &entries_read; + + status = dcerpc_wkssvc_NetrUseEnum_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUseEnum failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrUseEnum failed"); + + for (k=0; k < r.out.info->ctr.ctr0->count; k++) { + + use_name = r.out.info->ctr.ctr0->array[k].local; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + if (!test_NetrUseGetInfo_level(tctx, p, use_name, + levels[i], + WERR_OK)) + { + if (levels[i] != 0) { + return false; + } + } + } + + use_name = r.out.info->ctr.ctr0->array[k].remote; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + if (!test_NetrUseGetInfo_level(tctx, p, use_name, + levels[i], + WERR_NERR_USENOTFOUND)) + { + if (levels[i] != 0) { + return false; + } + } + } + } + + return true; +} + +static bool test_NetrLogonDomainNameAdd(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrLogonDomainNameAdd r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + + torture_comment(tctx, "Testing NetrLogonDomainNameAdd\n"); + + status = dcerpc_wkssvc_NetrLogonDomainNameAdd_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrLogonDomainNameAdd failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "NetrLogonDomainNameAdd failed"); + return true; +} + +static bool test_NetrLogonDomainNameDel(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrLogonDomainNameDel r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + + torture_comment(tctx, "Testing NetrLogonDomainNameDel\n"); + + status = dcerpc_wkssvc_NetrLogonDomainNameDel_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrLogonDomainNameDel failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "NetrLogonDomainNameDel failed"); + return true; +} + +static bool test_NetrEnumerateComputerNames_level(struct torture_context *tctx, + struct dcerpc_pipe *p, + uint16_t level, + const char ***names, + size_t *num_names) +{ + NTSTATUS status; + struct wkssvc_NetrEnumerateComputerNames r; + struct wkssvc_ComputerNamesCtr *ctr; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + ctr = talloc_zero(tctx, struct wkssvc_ComputerNamesCtr); + + r.in.server_name = dcerpc_server_name(p); + r.in.name_type = level; + r.in.Reserved = 0; + r.out.ctr = &ctr; + + torture_comment(tctx, "Testing NetrEnumerateComputerNames level %u\n", + r.in.name_type); + + status = dcerpc_wkssvc_NetrEnumerateComputerNames_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrEnumerateComputerNames failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrEnumerateComputerNames failed"); + + if ((level == NetPrimaryComputerName) && ctr->count != 1) { + torture_comment(tctx, + "NetrEnumerateComputerNames did not return one " + "name but %u\n", ctr->count); + return false; + } + + if (names && num_names) { + *num_names = 0; + *names = NULL; + for (i=0; i<ctr->count; i++) { + if (!add_string_to_array(tctx, + ctr->computer_name[i].string, + names, + num_names)) + { + return false; + } + } + } + + return true; +} + +static bool test_NetrEnumerateComputerNames(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + uint16_t levels[] = {0,1,2}; + int i; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + if (!test_NetrEnumerateComputerNames_level(tctx, + p, + levels[i], + NULL, NULL)) + { + return false; + } + } + + return true; +} + +static bool test_NetrValidateName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrValidateName r; + uint16_t levels[] = {0,1,2,3,4,5}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.name = lpcfg_workgroup(tctx->lp_ctx); + r.in.Account = NULL; + r.in.Password = NULL; + r.in.name_type = levels[i]; + + torture_comment(tctx, "Testing NetrValidateName level %u\n", + r.in.name_type); + + status = dcerpc_wkssvc_NetrValidateName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrValidateName failed"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_NOT_SUPPORTED, + "NetrValidateName failed"); + } + + return true; +} + +static bool test_NetrValidateName2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrValidateName2 r; + uint16_t levels[] = {0,1,2,3,4,5}; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + for (i=0; i<ARRAY_SIZE(levels); i++) { + + r.in.server_name = talloc_asprintf(tctx, "\\\\%s", dcerpc_server_name(p)); + r.in.name = lpcfg_workgroup(tctx->lp_ctx); + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.name_type = levels[i]; + + torture_comment(tctx, "Testing NetrValidateName2 level %u\n", + r.in.name_type); + + status = dcerpc_wkssvc_NetrValidateName2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrValidateName2 failed"); + torture_assert_werr_equal(tctx, r.out.result, + W_ERROR(HRES_ERROR_V(HRES_RPC_E_REMOTE_DISABLED)), + "NetrValidateName2 failed"); + } + + return true; +} + +static bool test_NetrAddAlternateComputerName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrAddAlternateComputerName r; + const char **names = NULL; + size_t num_names = 0; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.NewAlternateMachineName = SMBTORTURE_ALTERNATE_NAME; + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.Reserved = 0; + + torture_comment(tctx, "Testing NetrAddAlternateComputerName\n"); + + status = dcerpc_wkssvc_NetrAddAlternateComputerName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrAddAlternateComputerName failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrAddAlternateComputerName failed"); + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetAlternateComputerNames, + &names, &num_names)) + { + return false; + } + + for (i=0; i<num_names; i++) { + if (strequal(names[i], SMBTORTURE_ALTERNATE_NAME)) { + return true; + } + } + + torture_comment(tctx, "new alternate name not set\n"); + + return false; +} + +static bool test_NetrRemoveAlternateComputerName(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrRemoveAlternateComputerName r; + const char **names = NULL; + size_t num_names = 0; + int i; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.AlternateMachineNameToRemove = SMBTORTURE_ALTERNATE_NAME; + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.Reserved = 0; + + torture_comment(tctx, "Testing NetrRemoveAlternateComputerName\n"); + + status = dcerpc_wkssvc_NetrRemoveAlternateComputerName_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrRemoveAlternateComputerName failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrRemoveAlternateComputerName failed"); + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetAlternateComputerNames, + &names, &num_names)) + { + return false; + } + + for (i=0; i<num_names; i++) { + if (strequal(names[i], SMBTORTURE_ALTERNATE_NAME)) { + return false; + } + } + + return true; +} + +static bool test_NetrSetPrimaryComputername_name(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *name) +{ + NTSTATUS status; + struct wkssvc_NetrSetPrimaryComputername r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.primary_name = name; + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.Reserved = 0; + + status = dcerpc_wkssvc_NetrSetPrimaryComputername_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrSetPrimaryComputername failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrSetPrimaryComputername failed"); + return true; +} + + +static bool test_NetrSetPrimaryComputername(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + /* + add alternate, + check if there + store old primary + set new primary (alternate) + check if there + later: check if del is possible + set primary back to origin + check if there + del alternate + */ + + const char **names_o = NULL, **names = NULL; + size_t num_names_o = 0, num_names = 0; + + torture_comment(tctx, "Testing NetrSetPrimaryComputername\n"); + + if (!test_NetrAddAlternateComputerName(tctx, p)) { + return false; + } + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetPrimaryComputerName, + &names_o, &num_names_o)) + { + return false; + } + + if (num_names_o != 1) { + return false; + } + + if (!test_NetrSetPrimaryComputername_name(tctx, p, + SMBTORTURE_ALTERNATE_NAME)) + { + return false; + } + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetPrimaryComputerName, + &names, &num_names)) + { + return false; + } + + if (num_names != 1) { + return false; + } + + if (!strequal(names[0], SMBTORTURE_ALTERNATE_NAME)) { + torture_comment(tctx, + "name mismatch (%s != %s) after NetrSetPrimaryComputername!\n", + names[0], SMBTORTURE_ALTERNATE_NAME); + /*return false */; + } + + if (!test_NetrSetPrimaryComputername_name(tctx, p, + names_o[0])) + { + return false; + } + + if (!test_NetrRemoveAlternateComputerName(tctx, p)) { + return false; + } + + + return true; +} + +static bool test_NetrRenameMachineInDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrRenameMachineInDomain r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.NewMachineName = SMBTORTURE_MACHINE_NAME; + r.in.Account = NULL; + r.in.password = NULL; + r.in.RenameOptions = 0; + + torture_comment(tctx, "Testing NetrRenameMachineInDomain\n"); + + status = dcerpc_wkssvc_NetrRenameMachineInDomain_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrRenameMachineInDomain failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "NetrRenameMachineInDomain failed"); + return true; +} + +static bool test_NetrRenameMachineInDomain2_name(struct torture_context *tctx, + struct dcerpc_pipe *p, + const char *new_name) +{ + NTSTATUS status; + struct wkssvc_NetrRenameMachineInDomain2 r; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.NewMachineName = new_name; + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.RenameOptions = 0; + + status = dcerpc_wkssvc_NetrRenameMachineInDomain2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrRenameMachineInDomain2 failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrRenameMachineInDomain2 failed"); + return true; +} + +static bool test_NetrRenameMachineInDomain2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + const char **names_o = NULL, **names = NULL; + size_t num_names_o = 0, num_names = 0; + + torture_comment(tctx, "Testing NetrRenameMachineInDomain2\n"); + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetPrimaryComputerName, + &names_o, &num_names_o)) + { + return false; + } + + if (num_names_o != 1) { + return false; + } + + if (!test_NetrRenameMachineInDomain2_name(tctx, p, + SMBTORTURE_MACHINE_NAME)) + { + return false; + } + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetPrimaryComputerName, + &names, &num_names)) + { + return false; + } + + if (num_names != 1) { + return false; + } + + if (strequal(names[0], names_o[0])) { + test_NetrRenameMachineInDomain2_name(tctx, p, names_o[0]); + return false; + } + + if (!strequal(names[0], SMBTORTURE_MACHINE_NAME)) { + test_NetrRenameMachineInDomain2_name(tctx, p, names_o[0]); + return false; + } + + if (!test_NetrRenameMachineInDomain2_name(tctx, p, names_o[0])) + { + return false; + } + + if (!test_NetrEnumerateComputerNames_level(tctx, p, + NetPrimaryComputerName, + &names, &num_names)) + { + return false; + } + + if (num_names != 1) { + return false; + } + + if (!strequal(names[0], names_o[0])) { + return false; + } + + return true; +} + +static bool test_NetrWorkstationStatisticsGet(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrWorkstationStatisticsGet r; + struct wkssvc_NetrWorkstationStatistics *info; + struct dcerpc_binding_handle *b = p->binding_handle; + + ZERO_STRUCT(r); + + info = talloc_zero(tctx, struct wkssvc_NetrWorkstationStatistics); + + r.in.server_name = dcerpc_server_name(p); + r.out.info = &info; + + torture_comment(tctx, "Testing NetrWorkstationStatisticsGet\n"); + + status = dcerpc_wkssvc_NetrWorkstationStatisticsGet_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrWorkstationStatisticsGet failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrWorkstationStatisticsGet failed"); + return true; +} + +/* only succeeds as long as the local messenger service is running - Guenther */ + +static bool test_NetrMessageBufferSend(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrMessageBufferSend r; + const char *message = SMBTORTURE_MESSAGE; + size_t size; + uint16_t *msg; + struct dcerpc_binding_handle *b = p->binding_handle; + + if (!push_ucs2_talloc(tctx, &msg, message, &size)) { + return false; + } + + r.in.server_name = dcerpc_server_name(p); + r.in.message_name = dcerpc_server_name(p); + r.in.message_sender_name = dcerpc_server_name(p); + r.in.message_buffer = (uint8_t *)msg; + r.in.message_size = size; + + torture_comment(tctx, "Testing NetrMessageBufferSend\n"); + + status = dcerpc_wkssvc_NetrMessageBufferSend_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrMessageBufferSend failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrMessageBufferSend failed"); + return true; +} + +static bool test_NetrGetJoinInformation(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrGetJoinInformation r; + enum wkssvc_NetJoinStatus join_status; + const char *name_buffer = ""; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.name_buffer = r.out.name_buffer = &name_buffer; + r.out.name_type = &join_status; + + torture_comment(tctx, "Testing NetrGetJoinInformation\n"); + + status = dcerpc_wkssvc_NetrGetJoinInformation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrGetJoinInformation failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrGetJoinInformation failed"); + return true; +} + +static bool test_GetJoinInformation(struct torture_context *tctx, + struct dcerpc_pipe *p, + enum wkssvc_NetJoinStatus *join_status_p, + const char **name) +{ + NTSTATUS status; + struct wkssvc_NetrGetJoinInformation r; + enum wkssvc_NetJoinStatus join_status; + const char *name_buffer = ""; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.name_buffer = r.out.name_buffer = &name_buffer; + r.out.name_type = &join_status; + + status = dcerpc_wkssvc_NetrGetJoinInformation_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrGetJoinInformation failed"); + torture_assert_werr_ok(tctx, r.out.result, + "NetrGetJoinInformation failed"); + + if (join_status_p) { + *join_status_p = join_status; + } + + if (*name) { + *name = talloc_strdup(tctx, name_buffer); + } + + return true; + +} + +static bool test_NetrGetJoinableOus(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrGetJoinableOus r; + uint32_t num_ous = 0; + const char **ous = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + r.in.Account = NULL; + r.in.unknown = NULL; + r.in.num_ous = r.out.num_ous = &num_ous; + r.out.ous = &ous; + + torture_comment(tctx, "Testing NetrGetJoinableOus\n"); + + status = dcerpc_wkssvc_NetrGetJoinableOus_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetrGetJoinableOus failed"); + torture_assert_werr_equal(tctx, r.out.result, + WERR_NOT_SUPPORTED, + "NetrGetJoinableOus failed"); + + return true; +} + +static bool test_NetrGetJoinableOus2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrGetJoinableOus2 r; + uint32_t num_ous = 0; + const char **ous = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + r.in.server_name = dcerpc_server_name(p); + r.in.domain_name = lpcfg_workgroup(tctx->lp_ctx); + r.in.Account = NULL; + r.in.EncryptedPassword = NULL; + r.in.num_ous = r.out.num_ous = &num_ous; + r.out.ous = &ous; + + torture_comment(tctx, "Testing NetrGetJoinableOus2\n"); + + status = dcerpc_wkssvc_NetrGetJoinableOus2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, "NetrGetJoinableOus2 failed"); + torture_assert_werr_equal(tctx, r.out.result, + W_ERROR(HRES_ERROR_V(HRES_RPC_E_REMOTE_DISABLED)), + "NetrGetJoinableOus2 failed"); + + return true; +} + +static bool test_NetrUnjoinDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUnjoinDomain r; + struct cli_credentials *creds = samba_cmdline_get_creds(); + const char *user = cli_credentials_get_username(creds); + const char *admin_account = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + admin_account = talloc_asprintf(tctx, "%s\\%s", + lpcfg_workgroup(tctx->lp_ctx), + user); + + r.in.server_name = dcerpc_server_name(p); + r.in.Account = admin_account; + r.in.password = NULL; + r.in.unjoin_flags = 0; + + torture_comment(tctx, "Testing NetrUnjoinDomain\n"); + + status = dcerpc_wkssvc_NetrUnjoinDomain_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUnjoinDomain failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "NetrUnjoinDomain failed"); + return true; +} + +static bool test_NetrJoinDomain(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrJoinDomain r; + struct cli_credentials *creds = samba_cmdline_get_creds(); + const char *user = cli_credentials_get_username(creds); + const char *admin_account = NULL; + struct dcerpc_binding_handle *b = p->binding_handle; + + admin_account = talloc_asprintf(tctx, "%s\\%s", + lpcfg_workgroup(tctx->lp_ctx), + user); + + r.in.server_name = dcerpc_server_name(p); + r.in.domain_name = lpcfg_dnsdomain(tctx->lp_ctx); + r.in.account_ou = NULL; + r.in.Account = admin_account; + r.in.password = NULL; + r.in.join_flags = 0; + + torture_comment(tctx, "Testing NetrJoinDomain\n"); + + status = dcerpc_wkssvc_NetrJoinDomain_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrJoinDomain failed"); + torture_assert_werr_equal(tctx, r.out.result, WERR_NOT_SUPPORTED, + "NetrJoinDomain failed"); + return true; +} + +/* + * prerequisites for remotely joining an unjoined XP SP2 workstation: + * - firewall needs to be disabled (or open for ncacn_np access) + * - HKLM\System\CurrentControlSet\Control\Lsa\forceguest needs to 0 + * see also: + * http://support.microsoft.com/kb/294355/EN-US/ and + * http://support.microsoft.com/kb/290403/EN-US/ + */ + +static bool test_NetrJoinDomain2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrJoinDomain2 r; + const char *domain_admin_account = NULL; + const char *domain_admin_password = NULL; + const char *domain_name = NULL; + struct wkssvc_PasswordBuffer *pwd_buf; + enum wkssvc_NetJoinStatus join_status; + const char *join_name = NULL; + WERROR expected_err; + WERROR werr; + DATA_BLOB session_key; + struct dcerpc_binding_handle *b = p->binding_handle; + + /* FIXME: this test assumes to join workstations / servers and does not + * handle DCs (WERR_NERR_SETUPDOMAINCONTROLLER) */ + + if (!test_GetJoinInformation(tctx, p, &join_status, &join_name)) + { + return false; + } + + switch (join_status) { + case NET_SETUP_DOMAIN_NAME: + expected_err = WERR_NERR_SETUPALREADYJOINED; + break; + case NET_SETUP_UNKNOWN_STATUS: + case NET_SETUP_UNJOINED: + case NET_SETUP_WORKGROUP_NAME: + default: + expected_err = WERR_OK; + break; + } + + domain_admin_account = torture_setting_string(tctx, "domain_admin_account", NULL); + + domain_admin_password = torture_setting_string(tctx, "domain_admin_password", NULL); + + domain_name = torture_setting_string(tctx, "domain_name", NULL); + + if ((domain_admin_account == NULL) || + (domain_admin_password == NULL) || + (domain_name == NULL)) { + torture_comment(tctx, "not enough input parameter\n"); + return false; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + werr = encode_wkssvc_join_password_buffer(tctx, + domain_admin_password, + &session_key, + &pwd_buf); + if (!W_ERROR_IS_OK(werr)) { + return false; + } + + r.in.server_name = dcerpc_server_name(p); + r.in.domain_name = domain_name; + r.in.account_ou = NULL; + r.in.admin_account = domain_admin_account; + r.in.encrypted_password = pwd_buf; + r.in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE | + WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE; + + torture_comment(tctx, "Testing NetrJoinDomain2 (assuming non-DC)\n"); + + status = dcerpc_wkssvc_NetrJoinDomain2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrJoinDomain2 failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_err, + "NetrJoinDomain2 failed"); + + if (!test_GetJoinInformation(tctx, p, &join_status, &join_name)) + { + return false; + } + + if (join_status != NET_SETUP_DOMAIN_NAME) { + torture_comment(tctx, + "Join verify failed: got %d\n", join_status); + return false; + } + + return true; +} + +static bool test_NetrUnjoinDomain2(struct torture_context *tctx, + struct dcerpc_pipe *p) +{ + NTSTATUS status; + struct wkssvc_NetrUnjoinDomain2 r; + const char *domain_admin_account = NULL; + const char *domain_admin_password = NULL; + struct wkssvc_PasswordBuffer *pwd_buf; + enum wkssvc_NetJoinStatus join_status; + const char *join_name = NULL; + WERROR expected_err; + WERROR werr; + DATA_BLOB session_key; + struct dcerpc_binding_handle *b = p->binding_handle; + + /* FIXME: this test assumes to join workstations / servers and does not + * handle DCs (WERR_NERR_SETUPDOMAINCONTROLLER) */ + + if (!test_GetJoinInformation(tctx, p, &join_status, &join_name)) + { + return false; + } + + switch (join_status) { + case NET_SETUP_UNJOINED: + expected_err = WERR_NERR_SETUPNOTJOINED; + break; + case NET_SETUP_DOMAIN_NAME: + case NET_SETUP_UNKNOWN_STATUS: + case NET_SETUP_WORKGROUP_NAME: + default: + expected_err = WERR_OK; + break; + } + + domain_admin_account = torture_setting_string(tctx, "domain_admin_account", NULL); + + domain_admin_password = torture_setting_string(tctx, "domain_admin_password", NULL); + + if ((domain_admin_account == NULL) || + (domain_admin_password == NULL)) { + torture_comment(tctx, "not enough input parameter\n"); + return false; + } + + status = dcerpc_fetch_session_key(p, &session_key); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + werr = encode_wkssvc_join_password_buffer(tctx, + domain_admin_password, + &session_key, + &pwd_buf); + if (!W_ERROR_IS_OK(werr)) { + return false; + } + + r.in.server_name = dcerpc_server_name(p); + r.in.account = domain_admin_account; + r.in.encrypted_password = pwd_buf; + r.in.unjoin_flags = 0; + + torture_comment(tctx, "Testing NetrUnjoinDomain2 (assuming non-DC)\n"); + + status = dcerpc_wkssvc_NetrUnjoinDomain2_r(b, tctx, &r); + torture_assert_ntstatus_ok(tctx, status, + "NetrUnjoinDomain2 failed"); + torture_assert_werr_equal(tctx, r.out.result, expected_err, + "NetrUnjoinDomain2 failed"); + + if (!test_GetJoinInformation(tctx, p, &join_status, &join_name)) + { + return false; + } + + switch (join_status) { + case NET_SETUP_UNJOINED: + case NET_SETUP_WORKGROUP_NAME: + break; + case NET_SETUP_UNKNOWN_STATUS: + case NET_SETUP_DOMAIN_NAME: + default: + torture_comment(tctx, + "Unjoin verify failed: got %d\n", join_status); + return false; + } + + return true; +} + + +struct torture_suite *torture_rpc_wkssvc(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite; + struct torture_rpc_tcase *tcase; + struct torture_test *test; + + suite = torture_suite_create(mem_ctx, "wkssvc"); + tcase = torture_suite_add_rpc_iface_tcase(suite, "wkssvc", + &ndr_table_wkssvc); + + torture_rpc_tcase_add_test(tcase, "NetWkstaGetInfo", + test_NetWkstaGetInfo); + + torture_rpc_tcase_add_test(tcase, "NetWkstaTransportEnum", + test_NetWkstaTransportEnum); + torture_rpc_tcase_add_test(tcase, "NetrWkstaTransportDel", + test_NetrWkstaTransportDel); + torture_rpc_tcase_add_test(tcase, "NetrWkstaTransportAdd", + test_NetrWkstaTransportAdd); + + torture_rpc_tcase_add_test(tcase, "NetWkstaEnumUsers", + test_NetWkstaEnumUsers); + torture_rpc_tcase_add_test(tcase, "NetrWkstaUserGetInfo", + test_NetrWkstaUserGetInfo); + + torture_rpc_tcase_add_test(tcase, "NetrUseDel", + test_NetrUseDel); + torture_rpc_tcase_add_test(tcase, "NetrUseGetInfo", + test_NetrUseGetInfo); + torture_rpc_tcase_add_test(tcase, "NetrUseEnum", + test_NetrUseEnum); + torture_rpc_tcase_add_test(tcase, "NetrUseAdd", + test_NetrUseAdd); + + torture_rpc_tcase_add_test(tcase, "NetrValidateName", + test_NetrValidateName); + torture_rpc_tcase_add_test(tcase, "NetrValidateName2", + test_NetrValidateName2); + torture_rpc_tcase_add_test(tcase, "NetrLogonDomainNameDel", + test_NetrLogonDomainNameDel); + torture_rpc_tcase_add_test(tcase, "NetrLogonDomainNameAdd", + test_NetrLogonDomainNameAdd); + torture_rpc_tcase_add_test(tcase, "NetrRemoveAlternateComputerName", + test_NetrRemoveAlternateComputerName); + torture_rpc_tcase_add_test(tcase, "NetrAddAlternateComputerName", + test_NetrAddAlternateComputerName); + test = torture_rpc_tcase_add_test(tcase, "NetrSetPrimaryComputername", + test_NetrSetPrimaryComputername); + test->dangerous = true; + test = torture_rpc_tcase_add_test(tcase, "NetrRenameMachineInDomain", + test_NetrRenameMachineInDomain); + test->dangerous = true; + test = torture_rpc_tcase_add_test(tcase, "NetrRenameMachineInDomain2", + test_NetrRenameMachineInDomain2); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "NetrEnumerateComputerNames", + test_NetrEnumerateComputerNames); + + test = torture_rpc_tcase_add_test(tcase, "NetrJoinDomain2", + test_NetrJoinDomain2); + test->dangerous = true; + test = torture_rpc_tcase_add_test(tcase, "NetrUnjoinDomain2", + test_NetrUnjoinDomain2); + test->dangerous = true; + + torture_rpc_tcase_add_test(tcase, "NetrJoinDomain", + test_NetrJoinDomain); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "NetrUnjoinDomain", + test_NetrUnjoinDomain); + test->dangerous = true; + torture_rpc_tcase_add_test(tcase, "NetrGetJoinInformation", + test_NetrGetJoinInformation); + torture_rpc_tcase_add_test(tcase, "NetrGetJoinableOus", + test_NetrGetJoinableOus); + torture_rpc_tcase_add_test(tcase, "NetrGetJoinableOus2", + test_NetrGetJoinableOus2); + + torture_rpc_tcase_add_test(tcase, "NetrWorkstationStatisticsGet", + test_NetrWorkstationStatisticsGet); + torture_rpc_tcase_add_test(tcase, "NetrMessageBufferSend", + test_NetrMessageBufferSend); + + return suite; +} |