/*
Samba Unix/Linux SMB client library
Distributed SMB/CIFS Server Management Utility
Copyright (C) 2011 Sumit Bose (sbose@redhat.com)
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 . */
#include "includes.h"
#include "utils/net.h"
#include "rpc_client/cli_pipe.h"
#include "rpc_client/cli_lsarpc.h"
#include "librpc/gen_ndr/ndr_drsblobs.h"
#include "../librpc/gen_ndr/ndr_lsa_c.h"
#include "../libcli/security/dom_sid.h"
#include "libsmb/libsmb.h"
#include "lib/crypto/gnutls_helpers.h"
#include
#include
#define ARG_OTHERSERVER "otherserver="
#define ARG_OTHERUSER "otheruser="
#define ARG_OTHERDOMAINSID "otherdomainsid="
#define ARG_OTHERDOMAIN "otherdomain="
#define ARG_OTHERNETBIOSDOMAIN "other_netbios_domain="
#define ARG_TRUSTPW "trustpw="
enum trust_op {
TRUST_CREATE,
TRUST_DELETE
};
struct other_dom_data {
char *host;
char *user_name;
char *domain_sid_str;
char *dns_domain_name;
char *domain_name;
};
struct dom_data {
struct dom_sid *domsid;
char *dns_domain_name;
char *domain_name;
};
static NTSTATUS close_handle(TALLOC_CTX *mem_ctx,
struct dcerpc_binding_handle *bind_hnd,
struct policy_handle *pol_hnd)
{
NTSTATUS status;
NTSTATUS result;
status = dcerpc_lsa_Close(bind_hnd, mem_ctx, pol_hnd, &result);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("dcerpc_lsa_Close failed with error [%s].\n",
nt_errstr(status)));
return status;
}
if (!NT_STATUS_IS_OK(result)) {
DEBUG(0, ("lsa close failed with error [%s].\n",
nt_errstr(result)));
return result;
}
return NT_STATUS_OK;
}
static NTSTATUS delete_trust(TALLOC_CTX *mem_ctx,
struct dcerpc_binding_handle *bind_hnd,
struct policy_handle *pol_hnd,
struct dom_sid *domsid)
{
NTSTATUS status;
struct lsa_DeleteTrustedDomain dr;
dr.in.handle = pol_hnd;
dr.in.dom_sid = domsid;
status = dcerpc_lsa_DeleteTrustedDomain_r(bind_hnd, mem_ctx, &dr);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("dcerpc_lsa_DeleteTrustedDomain_r failed with [%s]\n",
nt_errstr(status)));
return status;
}
if (!NT_STATUS_IS_OK(dr.out.result)) {
DEBUG(0, ("DeleteTrustedDomain returned [%s]\n",
nt_errstr(dr.out.result)));
return dr.out.result;
}
return NT_STATUS_OK;
}
static NTSTATUS create_trust(TALLOC_CTX *mem_ctx,
struct dcerpc_binding_handle *bind_hnd,
struct policy_handle *pol_hnd,
const char *trust_name,
const char *trust_name_dns,
struct dom_sid *domsid,
struct lsa_TrustDomainInfoAuthInfoInternal *authinfo)
{
NTSTATUS status;
struct lsa_CreateTrustedDomainEx2 r;
struct lsa_TrustDomainInfoInfoEx trustinfo;
struct policy_handle trustdom_handle;
bool is_nt4 = trust_name_dns == NULL;
if (!is_nt4) {
fprintf(stdout, "Creating AD trust\n");
trustinfo.trust_type = LSA_TRUST_TYPE_UPLEVEL;
trustinfo.trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
} else {
fprintf(stdout, "Creating NT4 trust\n");
trustinfo.trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
trustinfo.trust_attributes = 0;
trust_name_dns = trust_name;
}
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;
r.in.policy_handle = pol_hnd;
r.in.info = &trustinfo;
r.in.auth_info_internal = authinfo;
r.in.access_mask = LSA_TRUSTED_SET_POSIX | LSA_TRUSTED_SET_AUTH |
LSA_TRUSTED_QUERY_DOMAIN_NAME;
r.out.trustdom_handle = &trustdom_handle;
status = dcerpc_lsa_CreateTrustedDomainEx2_r(bind_hnd, mem_ctx, &r);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("dcerpc_lsa_CreateTrustedDomainEx2_r failed "
"with error [%s].\n", nt_errstr(status)));
return status;
}
if (!NT_STATUS_IS_OK(r.out.result)) {
DEBUG(0, ("CreateTrustedDomainEx2_r returned [%s].\n",
nt_errstr(r.out.result)));
return r.out.result;
}
return NT_STATUS_OK;
}
static NTSTATUS get_domain_info(TALLOC_CTX *mem_ctx,
struct dcerpc_binding_handle *bind_hdn,
struct policy_handle *pol_hnd,
struct dom_data *dom_data)
{
NTSTATUS status;
struct lsa_QueryInfoPolicy2 qr;
struct dom_sid_buf buf;
qr.in.handle = pol_hnd;
qr.in.level = LSA_POLICY_INFO_DNS;
status = dcerpc_lsa_QueryInfoPolicy2_r(bind_hdn, mem_ctx, &qr);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("dcerpc_lsa_QueryInfoPolicy2_r failed "
"with error [%s].\n", nt_errstr(status)));
return status;
}
if (!NT_STATUS_IS_OK(qr.out.result)) {
DEBUG(0, ("QueryInfoPolicy2 returned [%s].\n",
nt_errstr(qr.out.result)));
return qr.out.result;
}
dom_data->domain_name = talloc_strdup(mem_ctx,
(*qr.out.info)->dns.name.string);
dom_data->dns_domain_name = talloc_strdup(mem_ctx,
(*qr.out.info)->dns.dns_domain.string);
dom_data->domsid = dom_sid_dup(mem_ctx, (*qr.out.info)->dns.sid);
if (dom_data->domain_name == NULL ||
dom_data->dns_domain_name == NULL ||
dom_data->domsid == NULL) {
DEBUG(0, ("Copying domain data failed.\n"));
return NT_STATUS_NO_MEMORY;
}
DEBUG(0, ("Got the following domain info [%s][%s][%s].\n",
dom_data->domain_name, dom_data->dns_domain_name,
dom_sid_str_buf(dom_data->domsid, &buf)));
return NT_STATUS_OK;
}
static NTSTATUS connect_and_get_info(TALLOC_CTX *mem_ctx,
struct net_context *net_ctx,
struct cli_state **cli,
struct rpc_pipe_client **pipe_hnd,
struct policy_handle *pol_hnd,
struct dom_data *dom_data,
DATA_BLOB *session_key)
{
NTSTATUS status;
NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
uint32_t out_version = 0;
union lsa_revision_info out_revision_info = {
.info1 = {
.revision = 0,
},
};
status = net_make_ipc_connection_ex(net_ctx, NULL, NULL, NULL,
NET_FLAGS_PDC, cli);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("Failed to connect to [%s] with error [%s]\n",
net_ctx->opt_host, nt_errstr(status)));
return status;
}
status = cli_rpc_pipe_open_noauth(*cli, &ndr_table_lsarpc, pipe_hnd);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("Failed to initialise lsa pipe with error [%s]\n",
nt_errstr(status)));
return status;
}
status = dcerpc_lsa_open_policy_fallback(
(*pipe_hnd)->binding_handle,
mem_ctx,
(*pipe_hnd)->srv_name_slash,
false,
LSA_POLICY_VIEW_LOCAL_INFORMATION |
LSA_POLICY_TRUST_ADMIN |
LSA_POLICY_CREATE_SECRET,
&out_version,
&out_revision_info,
pol_hnd,
&result);
if (any_nt_status_not_ok(status, result, &status)) {
DBG_ERR("Failed to open policy handle: %s\n",
nt_errstr(result));
return status;
}
status = get_domain_info(mem_ctx, (*pipe_hnd)->binding_handle,
pol_hnd, dom_data);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("get_domain_info failed with error [%s].\n",
nt_errstr(status)));
return status;
}
status = cli_get_session_key(mem_ctx, *pipe_hnd, session_key);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0,("Error getting session_key of LSA pipe. Error was %s\n",
nt_errstr(status)));
return status;
}
return NT_STATUS_OK;
}
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 int parse_trust_args(TALLOC_CTX *mem_ctx, int argc, const char **argv, struct other_dom_data **_o, char **_trustpw)
{
size_t c;
struct other_dom_data *o = NULL;
char *trustpw = NULL;
int ret = EFAULT;
if (argc == 0) {
return EINVAL;
}
o = talloc_zero(mem_ctx, struct other_dom_data);
if (o == NULL) {
DEBUG(0, ("talloc_zero failed.\n"));
return ENOMEM;
}
for (c = 0; c < argc; c++) {
if (strnequal(argv[c], ARG_OTHERSERVER, sizeof(ARG_OTHERSERVER)-1)) {
o->host = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERSERVER)-1);
if (o->host == NULL) {
ret = ENOMEM;
goto failed;
}
} else if (strnequal(argv[c], ARG_OTHERUSER, sizeof(ARG_OTHERUSER)-1)) {
o->user_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERUSER)-1);
if (o->user_name == NULL) {
ret = ENOMEM;
goto failed;
}
} else if (strnequal(argv[c], ARG_OTHERDOMAINSID, sizeof(ARG_OTHERDOMAINSID)-1)) {
o->domain_sid_str = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERDOMAINSID)-1);
if (o->domain_sid_str == NULL) {
ret = ENOMEM;
goto failed;
}
} else if (strnequal(argv[c], ARG_OTHERDOMAIN, sizeof(ARG_OTHERDOMAIN)-1)) {
o->dns_domain_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERDOMAIN)-1);
if (o->dns_domain_name == NULL) {
ret = ENOMEM;
goto failed;
}
} else if (strnequal(argv[c], ARG_OTHERNETBIOSDOMAIN, sizeof(ARG_OTHERNETBIOSDOMAIN)-1)) {
o->domain_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERNETBIOSDOMAIN)-1);
if (o->domain_name == NULL) {
ret = ENOMEM;
goto failed;
}
} else if (strnequal(argv[c], ARG_TRUSTPW, sizeof(ARG_TRUSTPW)-1)) {
trustpw = talloc_strdup(mem_ctx, argv[c] + sizeof(ARG_TRUSTPW)-1);
if (trustpw == NULL) {
ret = ENOMEM;
goto failed;
}
} else {
DEBUG(0, ("Unsupported option [%s].\n", argv[c]));
ret = EINVAL;
goto failed;
}
}
*_o = o;
*_trustpw = trustpw;
return 0;
failed:
talloc_free(o);
talloc_free(trustpw);
return ret;
}
static void print_trust_delete_usage(void)
{
d_printf( "%s\n"
"net rpc trust delete [options]\n"
"\nOptions:\n"
"\totherserver=DC in other domain\n"
"\totheruser=Admin user in other domain\n"
"\totherdomainsid=SID of other domain\n"
"\nExamples:\n"
"\tnet rpc trust delete otherserver=oname otheruser=ouser -S lname -U luser\n"
"\tnet rpc trust delete otherdomainsid=S-... -S lname -U luser\n"
" %s\n",
_("Usage:"),
_("Remove trust between two domains"));
}
static void print_trust_usage(void)
{
d_printf( "%s\n"
"net rpc trust create [options]\n"
"\nOptions:\n"
"\totherserver=DC in other domain\n"
"\totheruser=Admin user in other domain\n"
"\totherdomainsid=SID of other domain\n"
"\tother_netbios_domain=NetBIOS/short name of other domain\n"
"\totherdomain=Full/DNS name of other domain (if not used, create an NT4 trust)\n"
"\ttrustpw=Trust password\n"
"\nExamples:\n"
"\tnet rpc trust create otherserver=oname otheruser=ouser -S lname -U luser\n"
"\tnet rpc trust create otherdomainsid=S-... other_netbios_domain=odom otherdomain=odom.org trustpw=secret -S lname -U luser\n"
" %s\n",
_("Usage:"),
_("Create trust between two domains"));
}
static int rpc_trust_common(struct net_context *net_ctx, int argc,
const char **argv, enum trust_op op)
{
TALLOC_CTX *mem_ctx;
NTSTATUS status;
int ret;
int success = -1;
struct cli_state *cli[2] = {NULL, NULL};
struct rpc_pipe_client *pipe_hnd[2] = {NULL, NULL};
DATA_BLOB session_key[2];
struct policy_handle pol_hnd[2];
struct lsa_TrustDomainInfoAuthInfoInternal authinfo;
DATA_BLOB auth_blob;
char *trust_pw = NULL;
struct other_dom_data *other_dom_data;
struct net_context *other_net_ctx = NULL;
struct dom_data dom_data[2];
void (*usage)(void);
ZERO_STRUCT(session_key);
switch (op) {
case TRUST_CREATE:
usage = print_trust_usage;
break;
case TRUST_DELETE:
usage = print_trust_delete_usage;
break;
default:
DEBUG(0, ("Unsupported trust operation.\n"));
return -1;
}
if (net_ctx->display_usage) {
usage();
return 0;
}
mem_ctx = talloc_init("trust op");
if (mem_ctx == NULL) {
DEBUG(0, ("talloc_init failed.\n"));
return -1;
}
ret = parse_trust_args(mem_ctx, argc, argv, &other_dom_data, &trust_pw);
if (ret != 0) {
if (ret == EINVAL) {
usage();
} else {
DEBUG(0, ("Failed to parse arguments.\n"));
}
goto done;
}
if (other_dom_data->host != 0) {
other_net_ctx = talloc_zero(other_dom_data, struct net_context);
if (other_net_ctx == NULL) {
DEBUG(0, ("talloc_zero failed.\n"));
goto done;
}
other_net_ctx->opt_host = other_dom_data->host;
other_net_ctx->creds = cli_credentials_init(other_net_ctx);
cli_credentials_parse_string(other_net_ctx->creds,
other_dom_data->user_name,
CRED_SPECIFIED);
} else {
dom_data[1].domsid = dom_sid_parse_talloc(mem_ctx,
other_dom_data->domain_sid_str);
dom_data[1].domain_name = other_dom_data->domain_name;
dom_data[1].dns_domain_name = other_dom_data->dns_domain_name;
if (dom_data[1].dns_domain_name == NULL) {
fprintf(stdout, "No DNS domain name passed, "
"assuming NT4 trust!\n");
}
if (dom_data[1].domsid == NULL ||
(op == TRUST_CREATE &&
(dom_data[1].domain_name == NULL))) {
DEBUG(0, ("Missing required argument.\n"));
usage();
goto done;
}
}
status = connect_and_get_info(mem_ctx, net_ctx, &cli[0], &pipe_hnd[0],
&pol_hnd[0], &dom_data[0], &session_key[0]);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("connect_and_get_info failed with error [%s]\n",
nt_errstr(status)));
goto done;
}
if (other_net_ctx != NULL) {
status = connect_and_get_info(mem_ctx, other_net_ctx,
&cli[1], &pipe_hnd[1],
&pol_hnd[1], &dom_data[1],
&session_key[1]);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("connect_and_get_info failed with error [%s]\n",
nt_errstr(status)));
goto done;
}
}
if (op == TRUST_CREATE) {
gnutls_cipher_hd_t cipher_hnd = NULL;
gnutls_datum_t enc_session_key = {
.data = session_key[0].data,
.size = session_key[0].length,
};
int rc;
if (trust_pw == NULL) {
if (other_net_ctx == NULL) {
DEBUG(0, ("Missing either trustpw or otherhost.\n"));
goto done;
}
DEBUG(0, ("Using random trust password.\n"));
trust_pw = trust_pw_new_value(mem_ctx,
SEC_CHAN_DOMAIN,
SEC_DOMAIN);
if (trust_pw == NULL) {
DEBUG(0, ("generate_random_password failed.\n"));
goto done;
}
} else {
DEBUG(0, ("Using user provided password.\n"));
}
if (!get_trust_domain_passwords_auth_blob(mem_ctx, trust_pw,
&auth_blob)) {
DEBUG(0, ("get_trust_domain_passwords_auth_blob failed\n"));
goto done;
}
authinfo.auth_blob.data = (uint8_t *)talloc_memdup(
mem_ctx,
auth_blob.data,
auth_blob.length);
if (authinfo.auth_blob.data == NULL) {
goto done;
}
authinfo.auth_blob.size = auth_blob.length;
rc = gnutls_cipher_init(&cipher_hnd,
GNUTLS_CIPHER_ARCFOUR_128,
&enc_session_key,
NULL);
if (rc < 0) {
status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
goto done;
}
rc = gnutls_cipher_encrypt(cipher_hnd,
authinfo.auth_blob.data,
authinfo.auth_blob.size);
gnutls_cipher_deinit(cipher_hnd);
if (rc < 0) {
status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
goto done;
}
status = create_trust(mem_ctx, pipe_hnd[0]->binding_handle,
&pol_hnd[0],
dom_data[1].domain_name,
dom_data[1].dns_domain_name,
dom_data[1].domsid,
&authinfo);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("create_trust failed with error [%s].\n",
nt_errstr(status)));
goto done;
}
if (other_net_ctx != NULL) {
talloc_free(authinfo.auth_blob.data);
authinfo.auth_blob.data = (uint8_t *)talloc_memdup(
mem_ctx,
auth_blob.data,
auth_blob.length);
if (authinfo.auth_blob.data == NULL) {
goto done;
}
authinfo.auth_blob.size = auth_blob.length;
enc_session_key = (gnutls_datum_t) {
.data = session_key[1].data,
.size = session_key[1].length,
};
rc = gnutls_cipher_init(&cipher_hnd,
GNUTLS_CIPHER_ARCFOUR_128,
&enc_session_key,
NULL);
if (rc < 0) {
status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
goto done;
}
rc = gnutls_cipher_encrypt(cipher_hnd,
authinfo.auth_blob.data,
authinfo.auth_blob.size);
gnutls_cipher_deinit(cipher_hnd);
if (rc < 0) {
status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
goto done;
}
status = create_trust(mem_ctx,
pipe_hnd[1]->binding_handle,
&pol_hnd[1],
dom_data[0].domain_name,
dom_data[0].dns_domain_name,
dom_data[0].domsid, &authinfo);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("create_trust failed with error [%s].\n",
nt_errstr(status)));
goto done;
}
}
} else if (op == TRUST_DELETE) {
status = delete_trust(mem_ctx, pipe_hnd[0]->binding_handle,
&pol_hnd[0], dom_data[1].domsid);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("delete_trust failed with [%s].\n",
nt_errstr(status)));
goto done;
}
if (other_net_ctx != NULL) {
status = delete_trust(mem_ctx,
pipe_hnd[1]->binding_handle,
&pol_hnd[1], dom_data[0].domsid);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("delete_trust failed with [%s].\n",
nt_errstr(status)));
goto done;
}
}
}
status = close_handle(mem_ctx, pipe_hnd[0]->binding_handle,
&pol_hnd[0]);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("close_handle failed with error [%s].\n",
nt_errstr(status)));
goto done;
}
if (other_net_ctx != NULL) {
status = close_handle(mem_ctx, pipe_hnd[1]->binding_handle,
&pol_hnd[1]);
if (!NT_STATUS_IS_OK(status)) {
DEBUG(0, ("close_handle failed with error [%s].\n",
nt_errstr(status)));
goto done;
}
}
success = 0;
done:
data_blob_clear_free(&session_key[0]);
data_blob_clear_free(&session_key[1]);
cli_shutdown(cli[0]);
cli_shutdown(cli[1]);
talloc_destroy(mem_ctx);
return success;
}
static int rpc_trust_create(struct net_context *net_ctx, int argc,
const char **argv)
{
return rpc_trust_common(net_ctx, argc, argv, TRUST_CREATE);
}
static int rpc_trust_delete(struct net_context *net_ctx, int argc,
const char **argv)
{
return rpc_trust_common(net_ctx, argc, argv, TRUST_DELETE);
}
int net_rpc_trust(struct net_context *c, int argc, const char **argv)
{
struct functable func[] = {
{
"create",
rpc_trust_create,
NET_TRANSPORT_RPC,
N_("Create trusts"),
N_("net rpc trust create\n"
" Create trusts")
},
{
"delete",
rpc_trust_delete,
NET_TRANSPORT_RPC,
N_("Remove trusts"),
N_("net rpc trust delete\n"
" Remove trusts")
},
{NULL, NULL, 0, NULL, NULL}
};
return net_run_function(c, argc, argv, "net rpc trust", func);
}