1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00

r15543: New implementation of 'net ads join' to be more like Windows XP.

The motivating factor is to not require more privileges for
the user account than Windows does when joining a domain.

The points of interest are

* net_ads_join() uses same rpc mechanisms as net_rpc_join()
* Enable CLDAP queries for filling in the majority of the
  ADS_STRUCT->config information
* Remove ldap_initialized() from sam/idmap_ad.c and
  libads/ldap.c
* Remove some unnecessary fields from ADS_STRUCT
* Manually set the dNSHostName and servicePrincipalName attribute
  using the machine account after the join

Thanks to Guenther and Simo for the review.

Still to do:

* Fix the userAccountControl for DES only systems
* Set the userPrincipalName in order to support things like
  'kinit -k' (although we might be able to just use the sAMAccountName
  instead)
* Re-add support for pre-creating the machine account in
  a specific OU
This commit is contained in:
Gerald Carter 2006-05-12 15:17:35 +00:00 committed by Gerald (Jerry) Carter
parent e4734cb99c
commit 4c4ea7b20f
16 changed files with 922 additions and 628 deletions

View File

@ -241,7 +241,7 @@ LIBADS_OBJ = libads/ldap.o libads/ldap_printer.o libads/sasl.o \
libads/krb5_setpw.o libads/ldap_user.o \
libads/ads_struct.o libads/kerberos_keytab.o \
libads/disp_sec.o libads/ads_utils.o libads/ldap_utils.o \
libads/authdata.o
libads/authdata.o libads/cldap.o
LIBADS_SERVER_OBJ = libads/util.o libads/kerberos_verify.o
@ -567,7 +567,7 @@ TOOL_OBJ = client/smbctool.o client/clitar.o $(PARAM_OBJ) $(LIBSMB_OBJ) \
$(LIB_NONSMBD_OBJ) $(KRBCLIENT_OBJ) \
$(READLINE_OBJ) $(POPT_LIB_OBJ) $(SECRETS_OBJ)
NET_OBJ1 = utils/net.o utils/net_ads.o utils/net_ads_cldap.o utils/net_help.o \
NET_OBJ1 = utils/net.o utils/net_ads.o utils/net_help.o \
utils/net_rap.o utils/net_rpc.o utils/net_rpc_samsync.o \
utils/net_rpc_join.o utils/net_time.o utils/net_lookup.o \
utils/net_cache.o utils/net_groupmap.o utils/net_idmap.o \

View File

@ -17,7 +17,6 @@ typedef struct {
char *realm;
char *workgroup;
char *ldap_server;
char *ldap_uri;
int foreign; /* set to 1 if connecting to a foreign realm */
} server;
@ -37,7 +36,6 @@ typedef struct {
struct {
char *realm;
char *bind_path;
char *schema_path;
char *ldap_server_name;
time_t current_time;
} config;
@ -219,19 +217,6 @@ typedef void **ADS_MODLIST;
#define GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP 0x00000004 /* 4 */
#define GTYPE_DISTRIBUTION_UNIVERSAL_GROUP 0x00000008 /* 8 */
/* Mailslot or cldap getdcname response flags */
#define ADS_PDC 0x00000001 /* DC is PDC */
#define ADS_GC 0x00000004 /* DC is a GC of forest */
#define ADS_LDAP 0x00000008 /* DC is an LDAP server */
#define ADS_DS 0x00000010 /* DC supports DS */
#define ADS_KDC 0x00000020 /* DC is running KDC */
#define ADS_TIMESERV 0x00000040 /* DC is running time services */
#define ADS_CLOSEST 0x00000080 /* DC is closest to client */
#define ADS_WRITABLE 0x00000100 /* DC has writable DS */
#define ADS_GOOD_TIMESERV 0x00000200 /* DC has hardware clock
(and running time) */
#define ADS_NDNC 0x00000400 /* DomainName is non-domain NC serviced
by LDAP server */
#define ADS_PINGS 0x0000FFFF /* Ping response */
#define ADS_DNS_CONTROLLER 0x20000000 /* DomainControllerName is a DNS name*/
#define ADS_DNS_DOMAIN 0x40000000 /* DomainName is a DNS name */

View File

@ -0,0 +1,58 @@
/*
Samba Unix/Linux SMB client library
net ads cldap functions
Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
Copyright (C) 2003 Jim McDonough (jmcd@us.ibm.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 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define MAX_DNS_LABEL 255 + 1
struct cldap_netlogon_reply {
uint32 type;
uint32 flags;
UUID_FLAT guid;
char forest[MAX_DNS_LABEL];
char domain[MAX_DNS_LABEL];
char hostname[MAX_DNS_LABEL];
char netbios_domain[MAX_DNS_LABEL];
char netbios_hostname[MAX_DNS_LABEL];
char unk[MAX_DNS_LABEL];
char user_name[MAX_DNS_LABEL];
char site_name[MAX_DNS_LABEL];
char site_name_2[MAX_DNS_LABEL];
uint32 version;
uint16 lmnt_token;
uint16 lm20_token;
};
/* Mailslot or cldap getdcname response flags */
#define ADS_PDC 0x00000001 /* DC is PDC */
#define ADS_GC 0x00000004 /* DC is a GC of forest */
#define ADS_LDAP 0x00000008 /* DC is an LDAP server */
#define ADS_DS 0x00000010 /* DC supports DS */
#define ADS_KDC 0x00000020 /* DC is running KDC */
#define ADS_TIMESERV 0x00000040 /* DC is running time services */
#define ADS_CLOSEST 0x00000080 /* DC is closest to client */
#define ADS_WRITABLE 0x00000100 /* DC has writable DS */
#define ADS_GOOD_TIMESERV 0x00000200 /* DC has hardware clock (and running time) */
#define ADS_NDNC 0x00000400 /* DomainName is non-domain NC serviced by LDAP server */

View File

@ -911,50 +911,29 @@ extern int errno;
#include "messages.h"
#include "charset.h"
#include "dynconfig.h"
#include "util_getent.h"
#include "debugparse.h"
#include "version.h"
#include "privileges.h"
#include "smb.h"
#include "ads_cldap.h"
#include "nameserv.h"
#include "secrets.h"
#include "byteorder.h"
#include "privileges.h"
#include "rpc_misc.h"
#include "rpc_dce.h"
#include "mapping.h"
#include "passdb.h"
#include "rpc_secdes.h"
#include "authdata.h"
#include "msdfs.h"
#include "rap.h"
#include "md5.h"
#include "hmacmd5.h"
#include "ntlmssp.h"
#include "auth.h"
#include "ntdomain.h"
#include "rpc_svcctl.h"
#include "rpc_ntsvcs.h"
#include "rpc_lsa.h"
@ -972,11 +951,8 @@ extern int errno;
#include "rpc_shutdown.h"
#include "rpc_perfcount.h"
#include "rpc_perfcount_defs.h"
#include "nt_printing.h"
#include "idmap.h"
#include "client.h"
#ifdef WITH_SMBWRAPPER
@ -984,21 +960,13 @@ extern int errno;
#endif
#include "session.h"
#include "asn_1.h"
#include "popt.h"
#include "mangle.h"
#include "module.h"
#include "nsswitch/winbind_client.h"
#include "spnego.h"
#include "rpc_client.h"
#include "event.h"
/*

View File

@ -118,12 +118,13 @@ void ads_destroy(ADS_STRUCT **ads)
is_mine = (*ads)->is_mine;
#if HAVE_LDAP
if ((*ads)->ld) ldap_unbind((*ads)->ld);
if ((*ads)->ld) {
ldap_unbind((*ads)->ld);
}
#endif
SAFE_FREE((*ads)->server.realm);
SAFE_FREE((*ads)->server.workgroup);
SAFE_FREE((*ads)->server.ldap_server);
SAFE_FREE((*ads)->server.ldap_uri);
SAFE_FREE((*ads)->auth.realm);
SAFE_FREE((*ads)->auth.password);
@ -132,7 +133,6 @@ void ads_destroy(ADS_STRUCT **ads)
SAFE_FREE((*ads)->config.realm);
SAFE_FREE((*ads)->config.bind_path);
SAFE_FREE((*ads)->config.schema_path);
SAFE_FREE((*ads)->config.ldap_server_name);
SAFE_FREE((*ads)->schema.sfu_uidnumber_attr);

View File

@ -20,33 +20,6 @@
*/
#include "includes.h"
#include "utils/net.h"
#ifdef HAVE_ADS
#define MAX_DNS_LABEL 255 + 1
struct cldap_netlogon_reply {
uint32 type;
uint32 flags;
UUID_FLAT guid;
char forest[MAX_DNS_LABEL];
char domain[MAX_DNS_LABEL];
char hostname[MAX_DNS_LABEL];
char netbios_domain[MAX_DNS_LABEL];
char netbios_hostname[MAX_DNS_LABEL];
char unk[MAX_DNS_LABEL];
char user_name[MAX_DNS_LABEL];
char site_name[MAX_DNS_LABEL];
char site_name_2[MAX_DNS_LABEL];
uint32 version;
uint16 lmnt_token;
uint16 lm20_token;
};
/*
These seem to be strings as described in RFC1035 4.1.4 and can be:
@ -272,94 +245,34 @@ static int recv_cldap_netlogon(int sock, struct cldap_netlogon_reply *reply)
return 0;
}
/*
do a cldap netlogon query
*/
int ads_cldap_netlogon(ADS_STRUCT *ads)
/*******************************************************************
do a cldap netlogon query. Always 389/udp
*******************************************************************/
BOOL ads_cldap_netlogon(const char *server, const char *realm, struct cldap_netlogon_reply *reply)
{
int sock;
int ret;
struct cldap_netlogon_reply reply;
const char *target = opt_host ? opt_host : inet_ntoa(ads->ldap_ip);
sock = open_udp_socket(target, ads->ldap_port);
sock = open_udp_socket(server, LDAP_PORT );
if (sock == -1) {
d_fprintf(stderr, "Failed to open udp socket to %s:%u\n",
inet_ntoa(ads->ldap_ip),
ads->ldap_port);
return -1;
DEBUG(2,("ads_cldap_netlogon: Failed to open udp socket to %s\n",
server));
return False;
}
ret = send_cldap_netlogon(sock, ads->config.realm, global_myname(), 6);
ret = send_cldap_netlogon(sock, realm, global_myname(), 6);
if (ret != 0) {
return ret;
return False;
}
ret = recv_cldap_netlogon(sock, &reply);
ret = recv_cldap_netlogon(sock, reply);
close(sock);
if (ret == -1) {
return -1;
return False;
}
d_printf("Information for Domain Controller: %s\n\n",
ads->config.ldap_server_name);
d_printf("Response Type: ");
switch (reply.type) {
case SAMLOGON_AD_UNK_R:
d_printf("SAMLOGON\n");
break;
case SAMLOGON_AD_R:
d_printf("SAMLOGON_USER\n");
break;
default:
d_printf("0x%x\n", reply.type);
break;
}
d_printf("GUID: %s\n",
smb_uuid_string_static(smb_uuid_unpack_static(reply.guid)));
d_printf("Flags:\n"
"\tIs a PDC: %s\n"
"\tIs a GC of the forest: %s\n"
"\tIs an LDAP server: %s\n"
"\tSupports DS: %s\n"
"\tIs running a KDC: %s\n"
"\tIs running time services: %s\n"
"\tIs the closest DC: %s\n"
"\tIs writable: %s\n"
"\tHas a hardware clock: %s\n"
"\tIs a non-domain NC serviced by LDAP server: %s\n",
(reply.flags & ADS_PDC) ? "yes" : "no",
(reply.flags & ADS_GC) ? "yes" : "no",
(reply.flags & ADS_LDAP) ? "yes" : "no",
(reply.flags & ADS_DS) ? "yes" : "no",
(reply.flags & ADS_KDC) ? "yes" : "no",
(reply.flags & ADS_TIMESERV) ? "yes" : "no",
(reply.flags & ADS_CLOSEST) ? "yes" : "no",
(reply.flags & ADS_WRITABLE) ? "yes" : "no",
(reply.flags & ADS_GOOD_TIMESERV) ? "yes" : "no",
(reply.flags & ADS_NDNC) ? "yes" : "no");
printf("Forest:\t\t\t%s\n", reply.forest);
printf("Domain:\t\t\t%s\n", reply.domain);
printf("Domain Controller:\t%s\n", reply.hostname);
printf("Pre-Win2k Domain:\t%s\n", reply.netbios_domain);
printf("Pre-Win2k Hostname:\t%s\n", reply.netbios_hostname);
if (*reply.unk) printf("Unk:\t\t\t%s\n", reply.unk);
if (*reply.user_name) printf("User name:\t%s\n", reply.user_name);
printf("Site Name:\t\t%s\n", reply.site_name);
printf("Site Name (2):\t\t%s\n", reply.site_name_2);
d_printf("NT Version: %d\n", reply.version);
d_printf("LMNT Token: %.2x\n", reply.lmnt_token);
d_printf("LM20 Token: %.2x\n", reply.lm20_token);
return ret;
return True;
}
#endif

View File

@ -112,31 +112,52 @@ static int ldap_search_with_timeout(LDAP *ld,
/*
try a connection to a given ldap server, returning True and setting the servers IP
in the ads struct if successful
TODO : add a negative connection cache in here leveraged off of the one
found in the rpc code. --jerry
*/
BOOL ads_try_connect(ADS_STRUCT *ads, const char *server, unsigned port)
BOOL ads_try_connect(ADS_STRUCT *ads, const char *server )
{
char *srv;
struct cldap_netlogon_reply cldap_reply;
if (!server || !*server) {
return False;
}
DEBUG(5,("ads_try_connect: trying ldap server '%s' port %u\n", server, port));
DEBUG(5,("ads_try_connect: sending CLDAP request to %s\n", server));
/* this copes with inet_ntoa brokenness */
srv = SMB_STRDUP(server);
ads->ld = ldap_open_with_timeout(srv, port, lp_ldap_timeout());
if (!ads->ld) {
free(srv);
ZERO_STRUCT( cldap_reply );
if ( !ads_cldap_netlogon( srv, ads->server.realm, &cldap_reply ) ) {
DEBUG(3,("ads_try_connect: CLDAP request %s failed.\n", srv));
return False;
}
ads->ldap_port = port;
/* Check the CLDAP reply flags */
if ( !(cldap_reply.flags & ADS_LDAP) ) {
DEBUG(1,("ads_try_connect: %s's CLDAP reply says it is not an LDAP server!\n",
srv));
SAFE_FREE( srv );
return False;
}
/* Fill in the ads->config values */
SAFE_FREE(ads->config.realm);
SAFE_FREE(ads->config.bind_path);
SAFE_FREE(ads->config.ldap_server_name);
ads->config.ldap_server_name = SMB_STRDUP(cldap_reply.hostname);
strupper_m(cldap_reply.domain);
ads->config.realm = SMB_STRDUP(cldap_reply.domain);
ads->config.bind_path = ads_build_dn(ads->config.realm);
ads->ldap_port = LDAP_PORT;
ads->ldap_ip = *interpret_addr2(srv);
free(srv);
SAFE_FREE(srv);
/* cache the successful connection */
@ -145,29 +166,6 @@ BOOL ads_try_connect(ADS_STRUCT *ads, const char *server, unsigned port)
return True;
}
/*
try a connection to a given ldap server, based on URL, returning True if successful
*/
static BOOL ads_try_connect_uri(ADS_STRUCT *ads)
{
#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
DEBUG(5,("ads_try_connect: trying ldap server at URI '%s'\n",
ads->server.ldap_uri));
if (ldap_initialize((LDAP**)&(ads->ld), ads->server.ldap_uri) == LDAP_SUCCESS) {
return True;
}
DEBUG(0, ("ldap_initialize: %s\n", strerror(errno)));
#else
DEBUG(1, ("no URL support in LDAP libs!\n"));
#endif
return False;
}
/**********************************************************************
Try to find an AD dc using our internal name resolution routines
Try the realm first and then then workgroup name if netbios is not
@ -233,8 +231,6 @@ again:
/* if we fail this loop, then giveup since all the IP addresses returned were dead */
for ( i=0; i<count; i++ ) {
/* since this is an ads conection request, default to LDAP_PORT is not set */
int port = (ip_list[i].port!=PORT_NONE) ? ip_list[i].port : LDAP_PORT;
fstring server;
fstrcpy( server, inet_ntoa(ip_list[i].ip) );
@ -242,7 +238,7 @@ again:
if ( !NT_STATUS_IS_OK(check_negative_conn_cache(realm, server)) )
continue;
if ( ads_try_connect(ads, server, port) ) {
if ( ads_try_connect(ads, server) ) {
SAFE_FREE(ip_list);
return True;
}
@ -270,16 +266,10 @@ ADS_STATUS ads_connect(ADS_STRUCT *ads)
ads->last_attempt = time(NULL);
ads->ld = NULL;
/* try with a URL based server */
if (ads->server.ldap_uri &&
ads_try_connect_uri(ads)) {
goto got_connection;
}
/* try with a user specified server */
if (ads->server.ldap_server &&
ads_try_connect(ads, ads->server.ldap_server, LDAP_PORT)) {
ads_try_connect(ads, ads->server.ldap_server)) {
goto got_connection;
}
@ -292,22 +282,12 @@ ADS_STATUS ads_connect(ADS_STRUCT *ads)
got_connection:
DEBUG(3,("Connected to LDAP server %s\n", inet_ntoa(ads->ldap_ip)));
status = ads_server_info(ads);
if (!ADS_ERR_OK(status)) {
DEBUG(1,("Failed to get ldap server info\n"));
return status;
}
ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
status = ADS_ERROR(smb_ldap_start_tls(ads->ld, version));
if (!ADS_ERR_OK(status)) {
return status;
}
if (!ads->auth.user_name) {
/* have to use the userPrincipalName value here and
not servicePrincipalName; found by Guenther Deschner @ Sernet */
not servicePrincipalName; found by Guenther Deschner @ Sernet.
Is this still correct? The comment does not match
the code. --jerry */
asprintf(&ads->auth.user_name, "host/%s", global_myname() );
}
@ -331,10 +311,35 @@ got_connection:
}
#endif
/* If the caller() requested no LDAP bind, then we are done */
if (ads->auth.flags & ADS_AUTH_NO_BIND) {
return ADS_SUCCESS;
}
/* Otherwise setup the TCP LDAP session */
if ( (ads->ld = ldap_open_with_timeout(ads->config.ldap_server_name,
LDAP_PORT, lp_ldap_timeout())) == NULL )
{
return ADS_ERROR(LDAP_OPERATIONS_ERROR);
}
ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
status = ADS_ERROR(smb_ldap_start_tls(ads->ld, version));
if (!ADS_ERR_OK(status)) {
return status;
}
/* fill in the current time and offsets */
status = ads_current_time( ads );
if ( !ADS_ERR_OK(status) ) {
return status;
}
/* Now do the bind */
if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
return ADS_ERROR(ldap_simple_bind_s( ads->ld, NULL, NULL));
}
@ -2464,7 +2469,7 @@ static time_t ads_parse_time(const char *str)
}
const char *ads_get_attrname_by_oid(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char * OID)
const char *ads_get_attrname_by_oid(ADS_STRUCT *ads, const char *schema_path, TALLOC_CTX *mem_ctx, const char * OID)
{
ADS_STATUS rc;
int count = 0;
@ -2482,8 +2487,8 @@ const char *ads_get_attrname_by_oid(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const
goto failed;
}
rc = ads_do_search_retry(ads, ads->config.schema_path,
LDAP_SCOPE_SUBTREE, expr, attrs, &res);
rc = ads_do_search_retry(ads, schema_path, LDAP_SCOPE_SUBTREE,
expr, attrs, &res);
if (!ADS_ERR_OK(rc)) {
goto failed;
}
@ -2513,87 +2518,49 @@ failed:
* @param ads connection to ads server
* @return status of search
**/
ADS_STATUS ads_server_info(ADS_STRUCT *ads)
ADS_STATUS ads_current_time(ADS_STRUCT *ads)
{
const char *attrs[] = {"ldapServiceName",
"currentTime",
"schemaNamingContext", NULL};
const char *attrs[] = {"currentTime", NULL};
ADS_STATUS status;
void *res;
char *value;
char *p;
char *timestr;
char *schema_path;
TALLOC_CTX *ctx;
ADS_STRUCT *ads_s = ads;
if (!(ctx = talloc_init("ads_server_info"))) {
return ADS_ERROR(LDAP_NO_MEMORY);
}
status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
/* establish a new ldap tcp session if necessary */
if ( !ads->ld ) {
if ( (ads_s = ads_init( ads->server.realm, ads->server.workgroup,
ads->server.ldap_server )) == NULL )
{
goto done;
}
ads_s->auth.flags = ADS_AUTH_ANON_BIND;
status = ads_connect( ads_s );
if ( !ADS_ERR_OK(status))
goto done;
}
status = ads_do_search(ads_s, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
if (!ADS_ERR_OK(status)) {
talloc_destroy(ctx);
return status;
goto done;
}
value = ads_pull_string(ads, ctx, res, "ldapServiceName");
if (!value) {
ads_msgfree(ads, res);
talloc_destroy(ctx);
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
}
timestr = ads_pull_string(ads, ctx, res, "currentTime");
timestr = ads_pull_string(ads_s, ctx, res, "currentTime");
if (!timestr) {
ads_msgfree(ads, res);
talloc_destroy(ctx);
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
status = ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
goto done;
}
schema_path = ads_pull_string(ads, ctx, res, "schemaNamingContext");
if (!schema_path) {
ads_msgfree(ads, res);
talloc_destroy(ctx);
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
}
SAFE_FREE(ads->config.schema_path);
ads->config.schema_path = SMB_STRDUP(schema_path);
ads_msgfree(ads, res);
p = strchr(value, ':');
if (!p) {
talloc_destroy(ctx);
DEBUG(1, ("ads_server_info: returned ldap server name did not contain a ':' "
"so was deemed invalid\n"));
return ADS_ERROR(LDAP_DECODING_ERROR);
}
SAFE_FREE(ads->config.ldap_server_name);
ads->config.ldap_server_name = SMB_STRDUP(p+1);
p = strchr(ads->config.ldap_server_name, '$');
if (!p || p[1] != '@') {
talloc_destroy(ctx);
DEBUG(1, ("ads_server_info: returned ldap server name (%s) does not contain '$@'"
" so was deemed invalid\n", ads->config.ldap_server_name));
SAFE_FREE(ads->config.ldap_server_name);
return ADS_ERROR(LDAP_DECODING_ERROR);
}
*p = 0;
SAFE_FREE(ads->config.realm);
SAFE_FREE(ads->config.bind_path);
ads->config.realm = SMB_STRDUP(p+2);
ads->config.bind_path = ads_build_dn(ads->config.realm);
DEBUG(3,("got ldap server name %s@%s, using bind path: %s\n",
ads->config.ldap_server_name, ads->config.realm,
ads->config.bind_path));
/* but save the time and offset in the original ADS_STRUCT */
ads->config.current_time = ads_parse_time(timestr);
if (ads->config.current_time != 0) {
@ -2601,9 +2568,44 @@ ADS_STATUS ads_server_info(ADS_STRUCT *ads)
DEBUG(4,("time offset is %d seconds\n", ads->auth.time_offset));
}
status = ADS_SUCCESS;
done:
/* free any temporary ads connections */
if ( ads_s != ads ) {
ads_destroy( &ads_s );
}
talloc_destroy(ctx);
return ADS_SUCCESS;
return status;
}
/*********************************************************************
*********************************************************************/
static ADS_STATUS ads_schema_path(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, char **schema_path)
{
ADS_STATUS status;
void *res;
const char *schema;
const char *attrs[] = { "schemaNamingContext", NULL };
status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
if (!ADS_ERR_OK(status)) {
return status;
}
if ( (schema = ads_pull_string(ads, mem_ctx, res, "schemaNamingContext")) == NULL ) {
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
}
if ( (*schema_path = talloc_strdup(mem_ctx, schema)) == NULL ) {
return ADS_ERROR(LDAP_NO_MEMORY);
}
ads_msgfree(ads, res);
return status;
}
/**
@ -2617,41 +2619,71 @@ BOOL ads_check_sfu_mapping(ADS_STRUCT *ads)
BOOL ret = False;
TALLOC_CTX *ctx = NULL;
const char *gidnumber, *uidnumber, *homedir, *shell, *gecos;
char *schema_path;
ADS_STRUCT *ads_s = ads;
ADS_STATUS status;
ctx = talloc_init("ads_check_sfu_mapping");
if (ctx == NULL)
if ( (ctx = talloc_init("ads_check_sfu_mapping")) == NULL ) {
goto done;
}
gidnumber = ads_get_attrname_by_oid(ads, ctx, ADS_ATTR_SFU_GIDNUMBER_OID);
/* establish a new ldap tcp session if necessary */
if ( !ads->ld ) {
if ( (ads_s = ads_init( ads->server.realm, ads->server.workgroup,
ads->server.ldap_server )) == NULL )
{
goto done;
}
ads_s->auth.flags = ADS_AUTH_ANON_BIND;
status = ads_connect( ads_s );
if ( !ADS_ERR_OK(status))
goto done;
}
status = ads_schema_path( ads, ctx, &schema_path );
if ( !ADS_ERR_OK(status) ) {
DEBUG(3,("ads_check_sfu_mapping: Unable to retrieve schema DN!\n"));
goto done;
}
gidnumber = ads_get_attrname_by_oid(ads_s, schema_path, ctx, ADS_ATTR_SFU_GIDNUMBER_OID);
if (gidnumber == NULL)
goto done;
ads->schema.sfu_gidnumber_attr = SMB_STRDUP(gidnumber);
uidnumber = ads_get_attrname_by_oid(ads, ctx, ADS_ATTR_SFU_UIDNUMBER_OID);
uidnumber = ads_get_attrname_by_oid(ads_s, schema_path, ctx, ADS_ATTR_SFU_UIDNUMBER_OID);
if (uidnumber == NULL)
goto done;
ads->schema.sfu_uidnumber_attr = SMB_STRDUP(uidnumber);
homedir = ads_get_attrname_by_oid(ads, ctx, ADS_ATTR_SFU_HOMEDIR_OID);
homedir = ads_get_attrname_by_oid(ads_s, schema_path, ctx, ADS_ATTR_SFU_HOMEDIR_OID);
if (homedir == NULL)
goto done;
ads->schema.sfu_homedir_attr = SMB_STRDUP(homedir);
shell = ads_get_attrname_by_oid(ads, ctx, ADS_ATTR_SFU_SHELL_OID);
shell = ads_get_attrname_by_oid(ads_s, schema_path, ctx, ADS_ATTR_SFU_SHELL_OID);
if (shell == NULL)
goto done;
ads->schema.sfu_shell_attr = SMB_STRDUP(shell);
gecos = ads_get_attrname_by_oid(ads, ctx, ADS_ATTR_SFU_GECOS_OID);
gecos = ads_get_attrname_by_oid(ads_s, schema_path, ctx, ADS_ATTR_SFU_GECOS_OID);
if (gecos == NULL)
goto done;
ads->schema.sfu_gecos_attr = SMB_STRDUP(gecos);
ret = True;
done:
if (ctx)
/* free any temporary ads connections */
if ( ads_s != ads ) {
ads_destroy( &ads_s );
}
if (ctx) {
talloc_destroy(ctx);
}
return ret;
}
@ -2679,81 +2711,6 @@ ADS_STATUS ads_domain_sid(ADS_STRUCT *ads, DOM_SID *sid)
return ADS_SUCCESS;
}
/* this is rather complex - we need to find the allternate (netbios) name
for the domain, but there isn't a simple query to do this. Instead
we look for the principle names on the DCs account and find one that has
the right form, then extract the netbios name of the domain from that
NOTE! better method is this:
bin/net -Uadministrator%XXXXX ads search '(&(objectclass=crossref)(dnsroot=VNET3.HOME.SAMBA.ORG))' nETBIOSName
but you need to force the bind path to match the configurationNamingContext from the rootDSE
*/
ADS_STATUS ads_workgroup_name(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char **workgroup)
{
char *expr;
ADS_STATUS rc;
char **principles;
char *prefix;
int prefix_length;
int i;
void *res;
const char *attrs[] = {"servicePrincipalName", NULL};
size_t num_principals;
(*workgroup) = NULL;
asprintf(&expr, "(&(objectclass=computer)(dnshostname=%s.%s))",
ads->config.ldap_server_name, ads->config.realm);
if (expr == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
rc = ads_search(ads, &res, expr, attrs);
free(expr);
if (!ADS_ERR_OK(rc)) {
return rc;
}
principles = ads_pull_strings(ads, mem_ctx, res,
"servicePrincipalName", &num_principals);
ads_msgfree(ads, res);
if (!principles) {
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
}
asprintf(&prefix, "HOST/%s.%s/",
ads->config.ldap_server_name,
ads->config.realm);
prefix_length = strlen(prefix);
for (i=0;principles[i]; i++) {
if (strnequal(principles[i], prefix, prefix_length) &&
!strequal(ads->config.realm, principles[i]+prefix_length) &&
!strchr(principles[i]+prefix_length, '.')) {
/* found an alternate (short) name for the domain. */
DEBUG(3,("Found alternate name '%s' for realm '%s'\n",
principles[i]+prefix_length,
ads->config.realm));
(*workgroup) = talloc_strdup(mem_ctx, principles[i]+prefix_length);
break;
}
}
free(prefix);
if (!*workgroup) {
return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
}
return ADS_SUCCESS;
}
/**
* find our site name
* @param ads connection to ads server

View File

@ -1024,70 +1024,62 @@ static BOOL resolve_hosts(const char *name, int name_type,
static BOOL resolve_ads(const char *name, int name_type,
struct ip_service **return_iplist, int *return_count)
{
#ifdef HAVE_ADS
if ( name_type == 0x1c ) {
int count, i = 0;
NTSTATUS status;
TALLOC_CTX *ctx;
struct dns_rr_srv *dcs = NULL;
int numdcs = 0;
int count, i = 0;
NTSTATUS status;
TALLOC_CTX *ctx;
struct dns_rr_srv *dcs = NULL;
int numdcs = 0;
if ( name_type != 0x1c )
return False;
/* try to lookup the _ldap._tcp.<domain> if we are using ADS */
if ( lp_security() != SEC_ADS )
return False;
DEBUG(5,("resolve_hosts: Attempting to resolve DC's for %s using DNS\n",
name));
DEBUG(5,("resolve_hosts: Attempting to resolve DC's for %s using DNS\n",
name));
if ( (ctx = talloc_init("resolve_ads")) == NULL ) {
DEBUG(0,("resolve_ads: talloc_init() failed!\n"));
return False;
}
status = ads_dns_query_dcs( ctx, name, &dcs, &numdcs );
if ( !NT_STATUS_IS_OK( status ) ) {
return False;
}
if ( (*return_iplist = SMB_MALLOC_ARRAY(struct ip_service, numdcs)) == NULL ) {
DEBUG(0,("resolve_ads: malloc failed for %d entries\n", count ));
return False;
}
i = 0;
while ( i < numdcs ) {
/* use the IP address from the SRV structure if we have one */
if ( is_zero_ip( dcs[i].ip ) )
(*return_iplist)[i].ip = *interpret_addr2(dcs[i].hostname);
else
(*return_iplist)[i].ip = dcs[i].ip;
(*return_iplist)[i].port = dcs[i].port;
/* make sure it is a valid IP. I considered checking the negative
connection cache, but this is the wrong place for it. Maybe only
as a hac. After think about it, if all of the IP addresses retuend
from DNS are dead, what hope does a netbios name lookup have?
The standard reason for falling back to netbios lookups is that
our DNS server doesn't know anything about the DC's -- jerry */
if ( is_zero_ip((*return_iplist)[i].ip) )
continue;
i++;
}
TALLOC_FREE( dcs );
*return_count = i;
return True;
} else
#endif /* HAVE_ADS */
{
if ( (ctx = talloc_init("resolve_ads")) == NULL ) {
DEBUG(0,("resolve_ads: talloc_init() failed!\n"));
return False;
}
status = ads_dns_query_dcs( ctx, name, &dcs, &numdcs );
if ( !NT_STATUS_IS_OK( status ) ) {
return False;
}
if ( (*return_iplist = SMB_MALLOC_ARRAY(struct ip_service, numdcs)) == NULL ) {
DEBUG(0,("resolve_ads: malloc failed for %d entries\n", count ));
return False;
}
i = 0;
while ( i < numdcs ) {
/* use the IP address from the SRV structure if we have one */
if ( is_zero_ip( dcs[i].ip ) )
(*return_iplist)[i].ip = *interpret_addr2(dcs[i].hostname);
else
(*return_iplist)[i].ip = dcs[i].ip;
(*return_iplist)[i].port = dcs[i].port;
/* make sure it is a valid IP. I considered checking the negative
connection cache, but this is the wrong place for it. Maybe only
as a hac. After think about it, if all of the IP addresses retuend
from DNS are dead, what hope does a netbios name lookup have?
The standard reason for falling back to netbios lookups is that
our DNS server doesn't know anything about the DC's -- jerry */
if ( is_zero_ip((*return_iplist)[i].ip) )
continue;
i++;
}
TALLOC_FREE( dcs );
*return_count = i;
return True;
}
/*******************************************************************
@ -1178,8 +1170,7 @@ BOOL internal_resolve_name(const char *name, int name_type,
}
} else if(strequal( tok, "ads")) {
/* deal with 0x1c names here. This will result in a
SRV record lookup for _ldap._tcp.<domain> if we
are using 'security = ads' */
SRV record lookup */
if (resolve_ads(name, name_type, return_iplist, return_count)) {
result = True;
goto done;

View File

@ -618,18 +618,11 @@ static void dcip_to_name( const char *domainname, const char *realm,
if ( lp_security() == SEC_ADS )
{
ADS_STRUCT *ads;
ADS_STATUS status;
ads = ads_init( realm, domainname, NULL );
ads->auth.flags |= ADS_AUTH_NO_BIND;
if ( !ads_try_connect( ads, inet_ntoa(ip), LDAP_PORT ) ) {
ads_destroy( &ads );
return;
}
status = ads_server_info(ads);
if ( !ADS_ERR_OK(status) ) {
if ( !ads_try_connect( ads, inet_ntoa(ip) ) ) {
ads_destroy( &ads );
return;
}

View File

@ -3158,7 +3158,7 @@ WERROR check_published_printers(void)
int n_services = lp_numservices();
NT_PRINTER_INFO_LEVEL *printer = NULL;
ads = ads_init(NULL, NULL, NULL);
ads = ads_init(lp_realm(), lp_workgroup(), NULL);
if (!ads) {
DEBUG(3, ("ads_init() failed\n"));
return WERR_SERVER_UNAVAILABLE;

View File

@ -78,10 +78,6 @@ static ADS_STRUCT *ad_idmap_cached_connection(void)
ADS_STATUS status;
BOOL local = False;
#ifdef ADS_AUTH_EXTERNAL_BIND
local = ((strncmp(ad_idmap_uri, "ldapi://", sizeof("ldapi://") - 1)) == 0);
#endif /* ADS_AUTH_EXTERNAL_BIND */
if (ad_idmap_ads != NULL) {
ads = ad_idmap_ads;
@ -105,40 +101,18 @@ static ADS_STRUCT *ad_idmap_cached_connection(void)
setenv("KRB5CCNAME", WINBIND_CCACHE_NAME, 1);
}
ads = ads_init(NULL, NULL, NULL);
ads = ads_init(lp_realm(), lp_workgroup(), NULL);
if (!ads) {
DEBUG(1,("ads_init failed\n"));
return NULL;
}
/* if ad_imap_uri is not empty we try to connect to
* the given URI in smb.conf. Else try to connect to
* one of the DCs
*/
if (*ad_idmap_uri != '\0') {
ads->server.ldap_uri = SMB_STRDUP(ad_idmap_uri);
if (ads->server.ldap_uri == NULL) {
return NULL;
}
}
else {
ads->server.ldap_uri = NULL;
ads->server.ldap_server = NULL;
}
/* the machine acct password might have change - fetch it every time */
SAFE_FREE(ads->auth.password);
ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
#ifdef ADS_AUTH_EXTERNAL_BIND
if (local)
ads->auth.flags |= ADS_AUTH_EXTERNAL_BIND;
else
#endif
{
/* the machine acct password might have change - fetch it every time */
SAFE_FREE(ads->auth.password);
ads->auth.password = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
SAFE_FREE(ads->auth.realm);
ads->auth.realm = SMB_STRDUP(lp_realm());
}
SAFE_FREE(ads->auth.realm);
ads->auth.realm = SMB_STRDUP(lp_realm());
status = ads_connect(ads);
if (!ADS_ERR_OK(status)) {

View File

@ -233,6 +233,29 @@ NTSTATUS connect_to_ipc_anonymous(struct cli_state **c,
}
}
/****************************************************************************
connect to \\server\ipc$ using KRB5
****************************************************************************/
NTSTATUS connect_to_ipc_krb5(struct cli_state **c,
struct in_addr *server_ip, const char *server_name)
{
NTSTATUS nt_status;
nt_status = cli_full_connection(c, NULL, server_name,
server_ip, opt_port,
"IPC$", "IPC",
opt_user_name, opt_workgroup,
opt_password, CLI_FULL_CONNECTION_USE_KERBEROS,
Undefined, NULL);
if (NT_STATUS_IS_OK(nt_status)) {
return nt_status;
} else {
DEBUG(1,("Cannot connect to server using kerberos. Error was %s\n", nt_errstr(nt_status)));
return nt_status;
}
}
/**
* Connect a server and open a given pipe
*
@ -304,8 +327,9 @@ int net_use_machine_password(void)
return 0;
}
BOOL net_find_server(unsigned flags, struct in_addr *server_ip, char **server_name)
BOOL net_find_server(const char *domain, unsigned flags, struct in_addr *server_ip, char **server_name)
{
const char *d = domain ? domain : opt_target_workgroup;
if (opt_host) {
*server_name = SMB_STRDUP(opt_host);
@ -325,23 +349,22 @@ BOOL net_find_server(unsigned flags, struct in_addr *server_ip, char **server_na
} else if (flags & NET_FLAGS_PDC) {
struct in_addr pdc_ip;
if (get_pdc_ip(opt_target_workgroup, &pdc_ip)) {
if (get_pdc_ip(d, &pdc_ip)) {
fstring dc_name;
if (is_zero_ip(pdc_ip))
return False;
if ( !name_status_find(opt_target_workgroup, 0x1b, 0x20, pdc_ip, dc_name) )
if ( !name_status_find(d, 0x1b, 0x20, pdc_ip, dc_name) )
return False;
*server_name = SMB_STRDUP(dc_name);
*server_ip = pdc_ip;
}
} else if (flags & NET_FLAGS_DMB) {
struct in_addr msbrow_ip;
/* if (!resolve_name(MSBROWSE, &msbrow_ip, 1)) */
if (!resolve_name(opt_target_workgroup, &msbrow_ip, 0x1B)) {
if (!resolve_name(d, &msbrow_ip, 0x1B)) {
DEBUG(1,("Unable to resolve domain browser via name lookup\n"));
return False;
} else {
@ -350,7 +373,7 @@ BOOL net_find_server(unsigned flags, struct in_addr *server_ip, char **server_na
*server_name = SMB_STRDUP(inet_ntoa(opt_dest_ip));
} else if (flags & NET_FLAGS_MASTER) {
struct in_addr brow_ips;
if (!resolve_name(opt_target_workgroup, &brow_ips, 0x1D)) {
if (!resolve_name(d, &brow_ips, 0x1D)) {
/* go looking for workgroups */
DEBUG(1,("Unable to resolve master browser via name lookup\n"));
return False;
@ -387,17 +410,27 @@ BOOL net_find_pdc(struct in_addr *server_ip, fstring server_name, const char *do
return False;
}
struct cli_state *net_make_ipc_connection( unsigned flags )
{
return net_make_ipc_connection_ex( NULL, NULL, NULL, flags );
}
struct cli_state *net_make_ipc_connection(unsigned flags)
struct cli_state *net_make_ipc_connection_ex( const char *domain, const char *server,
struct in_addr *ip, unsigned flags)
{
char *server_name = NULL;
struct in_addr server_ip;
struct cli_state *cli = NULL;
NTSTATUS nt_status;
if (!net_find_server(flags, &server_ip, &server_name)) {
d_fprintf(stderr, "\nUnable to find a suitable server\n");
return NULL;
if ( !server || !ip ) {
if (!net_find_server(domain, flags, &server_ip, &server_name)) {
d_fprintf(stderr, "Unable to find a suitable server\n");
return NULL;
}
} else {
server_name = SMB_STRDUP( server );
server_ip = *ip;
}
if (flags & NET_FLAGS_ANONYMOUS) {

View File

@ -68,24 +68,15 @@ struct rpc_sh_cmd {
/* MACROS & DEFINES */
#define NET_FLAGS_MASTER 1
#define NET_FLAGS_DMB 2
/* Would it be insane to set 'localhost' as the default
remote host for this operation?
For example, localhost is insane for a 'join' operation.
*/
#define NET_FLAGS_LOCALHOST_DEFAULT_INSANE 4
/* We want to find the PDC only */
#define NET_FLAGS_PDC 8
/* We want an anonymous connection */
#define NET_FLAGS_ANONYMOUS 16
/* don't open an RPC pipe */
#define NET_FLAGS_NO_PIPE 32
#define NET_FLAGS_MASTER 0x00000001
#define NET_FLAGS_DMB 0x00000002
#define NET_FLAGS_LOCALHOST_DEFAULT_INSANE 0x00000004 /* Would it be insane to set 'localhost'
as the default remote host for this
operation? For example, localhost
is insane for a 'join' operation. */
#define NET_FLAGS_PDC 0x00000008 /* PDC only */
#define NET_FLAGS_ANONYMOUS 0x00000010 /* use an anonymous connection */
#define NET_FLAGS_NO_PIPE 0x00000020 /* don't open an RPC pipe */
/* net share operation modes */
#define NET_MODE_SHARE_MIGRATE 1

View File

@ -23,6 +23,20 @@
#include "includes.h"
#include "utils/net.h"
/* Macro for checking RPC error codes to make things more readable */
#define CHECK_RPC_ERR(rpc, msg) \
if (!NT_STATUS_IS_OK(result = rpc)) { \
DEBUG(0, (msg ": %s\n", nt_errstr(result))); \
goto done; \
}
#define CHECK_RPC_ERR_DEBUG(rpc, debug_args) \
if (!NT_STATUS_IS_OK(result = rpc)) { \
DEBUG(0, debug_args); \
goto done; \
}
#ifdef HAVE_ADS
int net_ads_usage(int argc, const char **argv)
@ -64,6 +78,79 @@ int net_ads_usage(int argc, const char **argv)
}
/*
do a cldap netlogon query
*/
static int net_ads_cldap_netlogon(ADS_STRUCT *ads)
{
int ret;
struct cldap_netlogon_reply reply;
if ( !ads_cldap_netlogon( inet_ntoa(ads->ldap_ip), ads->server.realm, &reply ) ) {
d_fprintf(stderr, "CLDAP query failed!\n");
return -1;
}
d_printf("Information for Domain Controller: %s\n\n",
inet_ntoa(ads->ldap_ip));
d_printf("Response Type: ");
switch (reply.type) {
case SAMLOGON_AD_UNK_R:
d_printf("SAMLOGON\n");
break;
case SAMLOGON_AD_R:
d_printf("SAMLOGON_USER\n");
break;
default:
d_printf("0x%x\n", reply.type);
break;
}
d_printf("GUID: %s\n",
smb_uuid_string_static(smb_uuid_unpack_static(reply.guid)));
d_printf("Flags:\n"
"\tIs a PDC: %s\n"
"\tIs a GC of the forest: %s\n"
"\tIs an LDAP server: %s\n"
"\tSupports DS: %s\n"
"\tIs running a KDC: %s\n"
"\tIs running time services: %s\n"
"\tIs the closest DC: %s\n"
"\tIs writable: %s\n"
"\tHas a hardware clock: %s\n"
"\tIs a non-domain NC serviced by LDAP server: %s\n",
(reply.flags & ADS_PDC) ? "yes" : "no",
(reply.flags & ADS_GC) ? "yes" : "no",
(reply.flags & ADS_LDAP) ? "yes" : "no",
(reply.flags & ADS_DS) ? "yes" : "no",
(reply.flags & ADS_KDC) ? "yes" : "no",
(reply.flags & ADS_TIMESERV) ? "yes" : "no",
(reply.flags & ADS_CLOSEST) ? "yes" : "no",
(reply.flags & ADS_WRITABLE) ? "yes" : "no",
(reply.flags & ADS_GOOD_TIMESERV) ? "yes" : "no",
(reply.flags & ADS_NDNC) ? "yes" : "no");
printf("Forest:\t\t\t%s\n", reply.forest);
printf("Domain:\t\t\t%s\n", reply.domain);
printf("Domain Controller:\t%s\n", reply.hostname);
printf("Pre-Win2k Domain:\t%s\n", reply.netbios_domain);
printf("Pre-Win2k Hostname:\t%s\n", reply.netbios_hostname);
if (*reply.unk) printf("Unk:\t\t\t%s\n", reply.unk);
if (*reply.user_name) printf("User name:\t%s\n", reply.user_name);
printf("Site Name:\t\t%s\n", reply.site_name);
printf("Site Name (2):\t\t%s\n", reply.site_name_2);
d_printf("NT Version: %d\n", reply.version);
d_printf("LMNT Token: %.2x\n", reply.lmnt_token);
d_printf("LM20 Token: %.2x\n", reply.lm20_token);
return ret;
}
/*
this implements the CLDAP based netlogon lookup requests
for finding the domain controller of a ADS domain
@ -93,7 +180,7 @@ static int net_ads_lookup(int argc, const char **argv)
ads->ldap_port = 389;
}
return ads_cldap_netlogon(ads);
return net_ads_cldap_netlogon(ads);
}
@ -102,14 +189,7 @@ static int net_ads_info(int argc, const char **argv)
{
ADS_STRUCT *ads;
/* if netbios is disabled we have to default to the realm from smb.conf */
if ( lp_disable_netbios() && *lp_realm() )
ads = ads_init(lp_realm(), opt_target_workgroup, opt_host);
else
ads = ads_init(NULL, opt_target_workgroup, opt_host);
if (ads) {
if ( (ads = ads_init(lp_realm(), opt_target_workgroup, opt_host)) != NULL ) {
ads->auth.flags |= ADS_AUTH_NO_BIND;
}
@ -120,6 +200,13 @@ static int net_ads_info(int argc, const char **argv)
return -1;
}
/* Try to set the server's current time since we didn't do a full
TCP LDAP session initially */
if ( !ADS_ERR_OK(ads_current_time( ads )) ) {
d_fprintf( stderr, "Failed to get server's current time!\n");
}
d_printf("LDAP server: %s\n", inet_ntoa(ads->ldap_ip));
d_printf("LDAP server name: %s\n", ads->config.ldap_server_name);
d_printf("Realm: %s\n", ads->config.realm);
@ -212,10 +299,19 @@ retry:
int net_ads_check(void)
{
ADS_STRUCT *ads;
ADS_STATUS status;
ads = ads_startup();
if (!ads)
if ( (ads = ads_init( lp_realm(), lp_workgroup(), NULL )) == NULL ) {
return -1;
}
ads->auth.flags |= ADS_AUTH_NO_BIND;
status = ads_connect(ads);
if ( !ADS_ERR_OK(status) ) {
return -1;
}
ads_destroy(&ads);
return 0;
}
@ -226,28 +322,38 @@ int net_ads_check(void)
static int net_ads_workgroup(int argc, const char **argv)
{
ADS_STRUCT *ads;
TALLOC_CTX *ctx;
const char *workgroup;
ADS_STATUS status;
const char *realm = NULL;
struct cldap_netlogon_reply reply;
if (!(ads = ads_startup())) return -1;
if ( strequal(lp_workgroup(), opt_target_workgroup ) )
realm = lp_realm();
if (!(ctx = talloc_init("net_ads_workgroup"))) {
ads_destroy(&ads);
ads = ads_init(realm, opt_target_workgroup, opt_host);
if (ads) {
ads->auth.flags |= ADS_AUTH_NO_BIND;
}
status = ads_connect(ads);
if (!ADS_ERR_OK(status) || !ads) {
d_fprintf(stderr, "Didn't find the cldap server!\n");
return -1;
}
if (!ads->config.realm) {
ads->config.realm = CONST_DISCARD(char *, opt_target_workgroup);
ads->ldap_port = 389;
}
if ( !ads_cldap_netlogon( inet_ntoa(ads->ldap_ip), ads->server.realm, &reply ) ) {
d_fprintf(stderr, "CLDAP query failed!\n");
return -1;
}
if (!ADS_ERR_OK(ads_workgroup_name(ads, ctx, &workgroup))) {
d_fprintf(stderr, "Failed to find workgroup for realm '%s'\n",
ads->config.realm);
talloc_destroy(ctx);
ads_destroy(&ads);
return -1;
}
d_printf("Workgroup: %s\n", reply.netbios_domain);
d_printf("Workgroup: %s\n", workgroup);
talloc_destroy(ctx);
ads_destroy(&ads);
return 0;
}
@ -707,28 +813,14 @@ int net_ads_testjoin(int argc, const char **argv)
return 0;
}
/*
join a domain using ADS
*/
int net_ads_join(int argc, const char **argv)
{
ADS_STRUCT *ads;
ADS_STATUS rc;
char *password;
char *machine_account = NULL;
char *tmp_password;
const char *org_unit = NULL;
char *dn;
void *res;
DOM_SID dom_sid;
char *ou_str;
uint32 sec_channel_type = SEC_CHAN_WKSTA;
uint32 account_type = UF_WORKSTATION_TRUST_ACCOUNT;
const char *short_domain_name = NULL;
TALLOC_CTX *ctx = NULL;
/*******************************************************************
Simple configu checks before beginning the join
********************************************************************/
if (lp_server_role() == ROLE_STANDALONE) {
d_printf("cannot join as standalone machine\n");
static int check_ads_config( void )
{
if (lp_server_role() != ROLE_DOMAIN_MEMBER ) {
d_printf("Host is not configured as a member server.\n");
return -1;
}
@ -739,92 +831,397 @@ int net_ads_join(int argc, const char **argv)
return -1;
}
if (argc > 0) {
org_unit = argv[0];
if ( lp_security() == SEC_ADS && !*lp_realm()) {
d_fprintf(stderr, "realm must be set in in smb.conf for ADS "
"join to succeed.\n");
return -1;
}
if (!secrets_init()) {
DEBUG(1,("Failed to initialise secrets database\n"));
return -1;
}
return 0;
}
tmp_password = generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH);
password = SMB_STRDUP(tmp_password);
/*******************************************************************
Store the machine password and domain SID
********************************************************************/
if (!(ads = ads_startup())) {
static int store_domain_account( const char *domain, DOM_SID *sid, const char *pw )
{
if (!secrets_store_domain_sid(domain, sid)) {
DEBUG(1,("Failed to save domain sid\n"));
return -1;
}
if (!*lp_realm()) {
d_fprintf(stderr, "realm must be set in in smb.conf for ADS join to succeed.\n");
ads_destroy(&ads);
if (!secrets_store_machine_password(pw, domain, SEC_CHAN_WKSTA)) {
DEBUG(1,("Failed to save machine password\n"));
return -1;
}
return 0;
}
/*******************************************************************
********************************************************************/
static NTSTATUS join_fetch_domain_sid( TALLOC_CTX *mem_ctx, struct cli_state *cli, DOM_SID **sid )
{
struct rpc_pipe_client *pipe_hnd = NULL;
POLICY_HND lsa_pol;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
char *domain = NULL;
if ( (pipe_hnd = cli_rpc_pipe_open_noauth(cli, PI_LSARPC, &status)) == NULL ) {
DEBUG(0, ("Error connecting to LSA pipe. Error was %s\n",
nt_errstr(status) ));
return status;
}
status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, True,
SEC_RIGHTS_MAXIMUM_ALLOWED, &lsa_pol);
if ( !NT_STATUS_IS_OK(status) )
return status;
status = rpccli_lsa_query_info_policy(pipe_hnd, mem_ctx,
&lsa_pol, 5, &domain, sid);
if ( !NT_STATUS_IS_OK(status) )
return status;
rpccli_lsa_close(pipe_hnd, mem_ctx, &lsa_pol);
cli_rpc_pipe_close(pipe_hnd); /* Done with this pipe */
/* Bail out if domain didn't get set. */
if (!domain) {
DEBUG(0, ("Could not get domain name.\n"));
return NT_STATUS_UNSUCCESSFUL;
}
return NT_STATUS_OK;
}
/*******************************************************************
Do the domain join
********************************************************************/
static NTSTATUS join_create_machine( TALLOC_CTX *mem_ctx, struct cli_state *cli,
DOM_SID *dom_sid, const char *clear_pw )
{
struct rpc_pipe_client *pipe_hnd = NULL;
POLICY_HND sam_pol, domain_pol, user_pol;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
char *acct_name;
const char *const_acct_name;
uint32 user_rid;
uint32 num_rids, *name_types, *user_rids;
uint32 flags = 0x3e8;
uint32 acb_info = ACB_WSTRUST;
uchar pwbuf[516];
SAM_USERINFO_CTR ctr;
SAM_USER_INFO_24 p24;
SAM_USER_INFO_16 p16;
uchar md4_trust_password[16];
/* Open the domain */
if ( (pipe_hnd = cli_rpc_pipe_open_noauth(cli, PI_SAMR, &status)) == NULL ) {
DEBUG(0, ("Error connecting to SAM pipe. Error was %s\n",
nt_errstr(status) ));
return status;
}
status = rpccli_samr_connect(pipe_hnd, mem_ctx,
SEC_RIGHTS_MAXIMUM_ALLOWED, &sam_pol);
if ( !NT_STATUS_IS_OK(status) )
return status;
status = rpccli_samr_open_domain(pipe_hnd, mem_ctx, &sam_pol,
SEC_RIGHTS_MAXIMUM_ALLOWED, dom_sid, &domain_pol);
if ( !NT_STATUS_IS_OK(status) )
return status;
/* Create domain user */
acct_name = talloc_asprintf(mem_ctx, "%s$", global_myname());
strlower_m(acct_name);
const_acct_name = acct_name;
status = rpccli_samr_create_dom_user(pipe_hnd, mem_ctx, &domain_pol,
acct_name, acb_info, 0xe005000b, &user_pol, &user_rid);
if ( !NT_STATUS_IS_OK(status)
&& !NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS))
{
d_fprintf(stderr, "Creation of workstation account failed\n");
/* If NT_STATUS_ACCESS_DENIED then we have a valid
username/password combo but the user does not have
administrator access. */
if (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_ACCESS_DENIED))
d_fprintf(stderr, "User specified does not have administrator privileges\n");
return status;
}
/* We *must* do this.... don't ask... */
if (NT_STATUS_IS_OK(status)) {
rpccli_samr_close(pipe_hnd, mem_ctx, &user_pol);
}
status = rpccli_samr_lookup_names(pipe_hnd, mem_ctx,
&domain_pol, flags, 1, &const_acct_name,
&num_rids, &user_rids, &name_types);
if ( !NT_STATUS_IS_OK(status) )
return status;
if ( name_types[0] != SID_NAME_USER) {
DEBUG(0, ("%s is not a user account (type=%d)\n", acct_name, name_types[0]));
return NT_STATUS_INVALID_WORKSTATION;
}
user_rid = user_rids[0];
/* Open handle on user */
status = rpccli_samr_open_user(pipe_hnd, mem_ctx, &domain_pol,
SEC_RIGHTS_MAXIMUM_ALLOWED, user_rid, &user_pol);
/* Create a random machine account password */
E_md4hash( clear_pw, md4_trust_password);
encode_pw_buffer(pwbuf, clear_pw, STR_UNICODE);
/* Set password on machine account */
ZERO_STRUCT(ctr);
ZERO_STRUCT(p24);
init_sam_user_info24(&p24, (char *)pwbuf,24);
ctr.switch_value = 24;
ctr.info.id24 = &p24;
status = rpccli_samr_set_userinfo(pipe_hnd, mem_ctx, &user_pol,
24, &cli->user_session_key, &ctr);
/* Why do we have to try to (re-)set the ACB to be the same as what
we passed in the samr_create_dom_user() call? When a NT
workstation is joined to a domain by an administrator the
acb_info is set to 0x80. For a normal user with "Add
workstations to the domain" rights the acb_info is 0x84. I'm
not sure whether it is supposed to make a difference or not. NT
seems to cope with either value so don't bomb out if the set
userinfo2 level 0x10 fails. -tpot */
ZERO_STRUCT(ctr);
ctr.switch_value = 16;
ctr.info.id16 = &p16;
init_sam_user_info16(&p16, acb_info);
/* Ignoring the return value is necessary for joining a domain
as a normal user with "Add workstation to domain" privilege. */
status = rpccli_samr_set_userinfo2(pipe_hnd, mem_ctx, &user_pol, 16,
&cli->user_session_key, &ctr);
rpccli_samr_close(pipe_hnd, mem_ctx, &user_pol);
cli_rpc_pipe_close(pipe_hnd); /* Done with this pipe */
return status;
}
/*******************************************************************
Do the domain join
********************************************************************/
static int net_join_domain( TALLOC_CTX *ctx, const char *servername,
struct in_addr *ip, DOM_SID **dom_sid, const char *password )
{
int ret = -1;
struct cli_state *cli = NULL;
if ( !NT_STATUS_IS_OK(connect_to_ipc_krb5(&cli, ip, servername)) )
goto done;
saf_store( cli->server_domain, cli->desthost );
if ( !NT_STATUS_IS_OK(join_fetch_domain_sid( ctx, cli, dom_sid )) )
goto done;
if ( !NT_STATUS_IS_OK(join_create_machine( ctx, cli, *dom_sid, password )) )
goto done;
ret = 0;
done:
if ( cli )
cli_shutdown(cli);
return ret;
}
/*******************************************************************
Set a machines dNSHostName and servicePrincipalName attributes
********************************************************************/
static ADS_STATUS net_set_machine_spn(TALLOC_CTX *ctx, ADS_STRUCT *ads_s )
{
ADS_STATUS status = ADS_ERROR(LDAP_SERVER_DOWN);
char *host_upn, *new_dn, *controlstr;
ADS_MODLIST mods;
const char *servicePrincipalName[3] = {NULL, NULL, NULL};
char *psp;
unsigned acct_control;
fstring my_fqdn;
LDAPMessage *res = NULL;
char *dn_string = NULL;
const char *machine_name = global_myname();
int count;
uint32 account_type;
if ( !machine_name ) {
return ADS_ERROR(LDAP_NO_MEMORY);
}
/* Find our DN */
status = ads_find_machine_acct(ads_s, (void **)(void *)&res, machine_name);
if (!ADS_ERR_OK(status))
return status;
if ( (count = ads_count_replies(ads_s, res)) != 1 ) {
DEBUG(1,("net_set_machine_spn: %d entries returned!\n", count));
return ADS_ERROR(LDAP_NO_MEMORY);
}
if ( (dn_string = ads_get_dn(ads_s, res)) == NULL ) {
DEBUG(1, ("ads_add_machine_acct: ads_get_dn returned NULL (malloc failure?)\n"));
goto done;
}
new_dn = talloc_strdup(ctx, dn_string);
ads_memfree(ads_s, dn_string);
if (!new_dn) {
return ADS_ERROR(LDAP_NO_MEMORY);
}
/* Windows only creates HOST/shortname & HOST/fqdn. We create
the UPN as well so that 'kinit -k' will work. You can only
request a TGT for entries with a UPN in AD. */
if ( !(psp = talloc_asprintf(ctx, "HOST/%s", machine_name)) )
goto done;
strupper_m(psp);
servicePrincipalName[0] = psp;
name_to_fqdn(my_fqdn, machine_name);
strlower_m(my_fqdn);
if ( !(psp = talloc_asprintf(ctx, "HOST/%s", my_fqdn)) )
goto done;
servicePrincipalName[1] = psp;
if (!(host_upn = talloc_asprintf(ctx, "%s@%s", servicePrincipalName[0], ads_s->config.realm)))
goto done;
/* set the account control string now */
acct_control = account_type | UF_DONT_EXPIRE_PASSWD;
#ifndef ENCTYPE_ARCFOUR_HMAC
acct_control |= UF_USE_DES_KEY_ONLY;
#endif
if (!(controlstr = talloc_asprintf(ctx, "%u", acct_control))) {
goto done;
}
/* now do the mods */
if (!(mods = ads_init_mods(ctx))) {
goto done;
}
/* fields of primary importance */
ads_mod_str(ctx, &mods, "dNSHostName", my_fqdn);
ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
#if 0
ads_mod_str(ctx, &mods, "userPrincipalName", host_upn);
ads_mod_str(ctx, &mods, "operatingSystem", "Samba");
ads_mod_str(ctx, &mods, "operatingSystemVersion", SAMBA_VERSION_STRING);
ads_mod_str(ctx, &mods, "userAccountControl", controlstr);
#endif
status = ads_gen_mod(ads_s, new_dn, mods);
done:
ads_msgfree(ads_s, res);
return status;
}
/*******************************************************************
join a domain using ADS (LDAP mods)
********************************************************************/
int net_ads_join(int argc, const char **argv)
{
ADS_STRUCT *ads, *ads_s;
ADS_STATUS status;
char *machine_account = NULL;
const char *short_domain_name = NULL;
char *tmp_password, *password;
struct cldap_netlogon_reply cldap_reply;
TALLOC_CTX *ctx;
DOM_SID *domain_sid = NULL;
if ( check_ads_config() != 0 ) {
d_fprintf(stderr, "Invalid configuration. Exiting....\n");
return -1;
}
if (!(ads = ads_init(lp_realm(), NULL, NULL ))) {
return -1;
}
ads->auth.flags = ADS_AUTH_NO_BIND;
status = ads_connect(ads);
if (strcmp(ads->config.realm, lp_realm()) != 0) {
d_fprintf(stderr, "realm of remote server (%s) and realm in smb.conf (%s) DO NOT match. Aborting join\n", ads->config.realm, lp_realm());
d_fprintf(stderr, "realm of remote server (%s) and realm in smb.conf "
"(%s) DO NOT match. Aborting join\n", ads->config.realm,
lp_realm());
ads_destroy(&ads);
return -1;
}
ou_str = ads_ou_string(ads,org_unit);
asprintf(&dn, "%s,%s", ou_str, ads->config.bind_path);
free(ou_str);
rc = ads_search_dn(ads, &res, dn, NULL);
ads_msgfree(ads, res);
if (rc.error_type == ENUM_ADS_ERROR_LDAP && rc.err.rc == LDAP_NO_SUCH_OBJECT) {
d_fprintf(stderr, "ads_join_realm: organizational unit %s does not exist (dn:%s)\n",
org_unit, dn);
ads_destroy(&ads);
return -1;
}
free(dn);
if (!ADS_ERR_OK(rc)) {
d_fprintf(stderr, "ads_join_realm: %s\n", ads_errstr(rc));
ads_destroy(&ads);
return -1;
}
rc = ads_join_realm(ads, global_myname(), account_type, org_unit);
if (!ADS_ERR_OK(rc)) {
d_fprintf(stderr, "ads_join_realm: %s\n", ads_errstr(rc));
ads_destroy(&ads);
if (!(ctx = talloc_init("net_join_domain"))) {
DEBUG(0, ("Could not initialise talloc context\n"));
return -1;
}
rc = ads_domain_sid(ads, &dom_sid);
if (!ADS_ERR_OK(rc)) {
d_fprintf(stderr, "ads_domain_sid: %s\n", ads_errstr(rc));
ads_destroy(&ads);
return -1;
}
/* Do the domain join here */
if (asprintf(&machine_account, "%s$", global_myname()) == -1) {
d_fprintf(stderr, "asprintf failed\n");
ads_destroy(&ads);
return -1;
}
rc = ads_set_machine_password(ads, machine_account, password);
if (!ADS_ERR_OK(rc)) {
d_fprintf(stderr, "ads_set_machine_password: %s\n", ads_errstr(rc));
ads_destroy(&ads);
tmp_password = generate_random_str(DEFAULT_TRUST_ACCOUNT_PASSWORD_LENGTH);
password = talloc_strdup(ctx, tmp_password);
if ( net_join_domain( ctx, ads->config.ldap_server_name, &ads->ldap_ip, &domain_sid, password ) != 0 ) {
d_fprintf(stderr, "Failed to join domain!\n");
return -1;
}
/* make sure we get the right workgroup */
/* Check the short name of the domain */
if ( !(ctx = talloc_init("net ads join")) ) {
d_fprintf(stderr, "talloc_init() failed!\n");
ads_destroy(&ads);
return -1;
}
ZERO_STRUCT( cldap_reply );
rc = ads_workgroup_name(ads, ctx, &short_domain_name);
if ( ADS_ERR_OK(rc) ) {
if ( ads_cldap_netlogon( ads->config.ldap_server_name,
ads->server.realm, &cldap_reply ) )
{
short_domain_name = talloc_strdup( ctx, cldap_reply.netbios_domain );
if ( !strequal(lp_workgroup(), short_domain_name) ) {
d_printf("The workgroup in smb.conf does not match the short\n");
d_printf("domain name obtained from the server.\n");
@ -836,25 +1233,66 @@ int net_ads_join(int argc, const char **argv)
}
d_printf("Using short domain name -- %s\n", short_domain_name);
/* HACK ALRET! Store the sid and password under bother the lp_workgroup()
/* HACK ALERT! Store the sid and password under both the lp_workgroup()
value from smb.conf and the string returned from the server. The former is
neede to bootstrap winbindd's first connection to the DC to get the real
short domain name --jerry */
if (!secrets_store_domain_sid(lp_workgroup(), &dom_sid)) {
DEBUG(1,("Failed to save domain sid\n"));
if ( (store_domain_account( lp_workgroup(), domain_sid, password ) == -1)
|| (store_domain_account( short_domain_name, domain_sid, password ) == -1) )
{
ads_destroy(&ads);
return -1;
}
if (!secrets_store_machine_password(password, lp_workgroup(), sec_channel_type)) {
DEBUG(1,("Failed to save machine password\n"));
/* Verify that everything is ok */
if ( net_rpc_join_ok(short_domain_name, ads->config.ldap_server_name, &ads->ldap_ip) != 0 ) {
d_fprintf(stderr, "Failed to verify membership in domain!\n");
return -1;
}
/* From here on out, use the machine account. But first delete any
existing tickets based on the user's creds. */
ads_kdestroy( NULL );
status = ADS_ERROR(LDAP_SERVER_DOWN);
ads_s = ads_init( ads->server.realm, ads->server.workgroup, ads->server.ldap_server );
if ( ads_s ) {
asprintf( &ads_s->auth.user_name, "%s$", global_myname() );
ads_s->auth.password = secrets_fetch_machine_password( short_domain_name, NULL, NULL );
ads_s->auth.realm = SMB_STRDUP( lp_realm() );
ads_kinit_password( ads_s );
status = ads_connect( ads_s );
}
if ( !ADS_ERR_OK(status) ) {
d_fprintf( stderr, "LDAP bind using machine credentials failed!\n");
d_fprintf(stderr, "Only NTLM authentication will be possible.\n");
} else {
/* create the dNSHostName & servicePrincipalName values */
status = net_set_machine_spn( ctx, ads_s );
if ( !ADS_ERR_OK(status) ) {
d_fprintf(stderr, "Failed to set servicePrincipalNames.\n");
d_fprintf(stderr, "Only NTLM authentication will be possible.\n");
/* don't fail */
}
}
ads_destroy( &ads_s );
#if defined(HAVE_KRB5)
if (asprintf(&machine_account, "%s$", global_myname()) == -1) {
d_fprintf(stderr, "asprintf failed\n");
ads_destroy(&ads);
return -1;
}
#ifdef HAVE_KRB5
if (!kerberos_derive_salting_principal(machine_account)) {
DEBUG(1,("Failed to determine salting principal\n"));
ads_destroy(&ads);
@ -866,36 +1304,25 @@ int net_ads_join(int argc, const char **argv)
ads_destroy(&ads);
return -1;
}
#endif
if (!secrets_store_domain_sid(short_domain_name, &dom_sid)) {
DEBUG(1,("Failed to save domain sid\n"));
ads_destroy(&ads);
return -1;
}
if (!secrets_store_machine_password(password, short_domain_name, sec_channel_type)) {
DEBUG(1,("Failed to save machine password\n"));
ads_destroy(&ads);
return -1;
}
/* Now build the keytab, using the same ADS connection */
if (lp_use_kerberos_keytab() && ads_keytab_create_default(ads)) {
DEBUG(1,("Error creating host keytab!\n"));
}
#endif
d_printf("Joined '%s' to realm '%s'\n", global_myname(), ads->config.realm);
SAFE_FREE(password);
SAFE_FREE(machine_account);
if ( ctx ) {
talloc_destroy(ctx);
}
TALLOC_FREE( ctx );
ads_destroy(&ads);
return 0;
}
/*******************************************************************
********************************************************************/
int net_ads_printer_usage(int argc, const char **argv)
{
d_printf(
@ -913,6 +1340,9 @@ int net_ads_printer_usage(int argc, const char **argv)
return -1;
}
/*******************************************************************
********************************************************************/
static int net_ads_printer_search(int argc, const char **argv)
{
ADS_STRUCT *ads;
@ -1549,6 +1979,7 @@ int net_ads_help(int argc, const char **argv)
#if 0
{"INFO", net_ads_info},
{"JOIN", net_ads_join},
{"JOIN2", net_ads_join2},
{"LEAVE", net_ads_leave},
{"STATUS", net_ads_status},
{"PASSWORD", net_ads_password},

View File

@ -6197,7 +6197,7 @@ BOOL net_rpc_check(unsigned flags)
char *server_name = NULL;
/* flags (i.e. server type) may depend on command */
if (!net_find_server(flags, &server_ip, &server_name))
if (!net_find_server(NULL, flags, &server_ip, &server_name))
return False;
ZERO_STRUCT(cli);

View File

@ -41,7 +41,7 @@
* @return A shell status integer (0 for success)
*
**/
static int net_rpc_join_ok(const char *domain)
int net_rpc_join_ok(const char *domain, const char *server, struct in_addr *ip )
{
uint32 neg_flags = NETLOGON_NEG_AUTH2_FLAGS|NETLOGON_NEG_SCHANNEL;
struct cli_state *cli = NULL;
@ -50,7 +50,7 @@ static int net_rpc_join_ok(const char *domain)
NTSTATUS ntret = NT_STATUS_UNSUCCESSFUL;
/* Connect to remote machine */
if (!(cli = net_make_ipc_connection(NET_FLAGS_ANONYMOUS | NET_FLAGS_PDC))) {
if (!(cli = net_make_ipc_connection_ex(domain, server, ip, (NET_FLAGS_ANONYMOUS|NET_FLAGS_PDC)))) {
return -1;
}
@ -402,7 +402,7 @@ int net_rpc_join_newstyle(int argc, const char **argv)
}
/* double-check, connection from scratch */
retval = net_rpc_join_ok(domain);
retval = net_rpc_join_ok(domain, cli->desthost, &cli->dest_ip);
done:
@ -434,7 +434,7 @@ int net_rpc_testjoin(int argc, const char **argv)
char *domain = smb_xstrdup(opt_target_workgroup);
/* Display success or failure */
if (net_rpc_join_ok(domain) != 0) {
if (net_rpc_join_ok(domain, NULL, NULL) != 0) {
fprintf(stderr,"Join to domain '%s' is not valid\n",domain);
free(domain);
return -1;