diff options
Diffstat (limited to 'source3/utils/net_ads.c')
-rw-r--r-- | source3/utils/net_ads.c | 2586 |
1 files changed, 2586 insertions, 0 deletions
diff --git a/source3/utils/net_ads.c b/source3/utils/net_ads.c new file mode 100644 index 0000000000..7dbe518c3d --- /dev/null +++ b/source3/utils/net_ads.c @@ -0,0 +1,2586 @@ +/* + Samba Unix/Linux SMB client library + net ads commands + Copyright (C) 2001 Andrew Tridgell (tridge@samba.org) + Copyright (C) 2001 Remus Koos (remuskoos@yahoo.com) + Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com) + Copyright (C) 2006 Gerald (Jerry) Carter (jerry@samba.org) + + 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 "utils/net.h" + +#ifdef HAVE_ADS + +/* when we do not have sufficient input parameters to contact a remote domain + * we always fall back to our own realm - Guenther*/ + +static const char *assume_own_realm(struct net_context *c) +{ + if (!c->opt_host && strequal(lp_workgroup(), c->opt_target_workgroup)) { + return lp_realm(); + } + + return NULL; +} + +/* + do a cldap netlogon query +*/ +static int net_ads_cldap_netlogon(struct net_context *c, ADS_STRUCT *ads) +{ + char addr[INET6_ADDRSTRLEN]; + struct nbt_cldap_netlogon_5 reply; + + print_sockaddr(addr, sizeof(addr), &ads->ldap.ss); + if ( !ads_cldap_netlogon_5(talloc_tos(), addr, ads->server.realm, &reply ) ) { + d_fprintf(stderr, "CLDAP query failed!\n"); + return -1; + } + + d_printf("Information for Domain Controller: %s\n\n", + addr); + + 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(talloc_tos(), reply.domain_uuid)); + + 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" + "\tIs NT6 DC that has some secrets: %s\n" + "\tIs NT6 DC that has all secrets: %s\n", + (reply.server_type & NBT_SERVER_PDC) ? "yes" : "no", + (reply.server_type & NBT_SERVER_GC) ? "yes" : "no", + (reply.server_type & NBT_SERVER_LDAP) ? "yes" : "no", + (reply.server_type & NBT_SERVER_DS) ? "yes" : "no", + (reply.server_type & NBT_SERVER_KDC) ? "yes" : "no", + (reply.server_type & NBT_SERVER_TIMESERV) ? "yes" : "no", + (reply.server_type & NBT_SERVER_CLOSEST) ? "yes" : "no", + (reply.server_type & NBT_SERVER_WRITABLE) ? "yes" : "no", + (reply.server_type & NBT_SERVER_GOOD_TIMESERV) ? "yes" : "no", + (reply.server_type & NBT_SERVER_NDNC) ? "yes" : "no", + (reply.server_type & NBT_SERVER_SELECT_SECRET_DOMAIN_6) ? "yes" : "no", + (reply.server_type & NBT_SERVER_FULL_SECRET_DOMAIN_6) ? "yes" : "no"); + + + printf("Forest:\t\t\t%s\n", reply.forest); + printf("Domain:\t\t\t%s\n", reply.dns_domain); + printf("Domain Controller:\t%s\n", reply.pdc_dns_name); + + printf("Pre-Win2k Domain:\t%s\n", reply.domain); + printf("Pre-Win2k Hostname:\t%s\n", reply.pdc_name); + + if (*reply.user_name) printf("User name:\t%s\n", reply.user_name); + + printf("Server Site Name :\t\t%s\n", reply.server_site); + printf("Client Site Name :\t\t%s\n", reply.client_site); + + d_printf("NT Version: %d\n", reply.nt_version); + d_printf("LMNT Token: %.2x\n", reply.lmnt_token); + d_printf("LM20 Token: %.2x\n", reply.lm20_token); + + return 0; +} + +/* + this implements the CLDAP based netlogon lookup requests + for finding the domain controller of a ADS domain +*/ +static int net_ads_lookup(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads lookup\n" + " Find the ADS DC using CLDAP lookup.\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup_nobind(c, false, &ads))) { + d_fprintf(stderr, "Didn't find the cldap server!\n"); + return -1; + } + + if (!ads->config.realm) { + ads->config.realm = CONST_DISCARD(char *, c->opt_target_workgroup); + ads->ldap.port = 389; + } + + return net_ads_cldap_netlogon(c, ads); +} + + + +static int net_ads_info(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + char addr[INET6_ADDRSTRLEN]; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads info\n" + " Display information about an Active Directory " + "server.\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup_nobind(c, false, &ads))) { + d_fprintf(stderr, "Didn't find the ldap server!\n"); + return -1; + } + + if (!ads || !ads->config.realm) { + d_fprintf(stderr, "Didn't find the ldap server!\n"); + 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"); + } + + print_sockaddr(addr, sizeof(addr), &ads->ldap.ss); + + d_printf("LDAP server: %s\n", addr); + d_printf("LDAP server name: %s\n", ads->config.ldap_server_name); + d_printf("Realm: %s\n", ads->config.realm); + d_printf("Bind Path: %s\n", ads->config.bind_path); + d_printf("LDAP port: %d\n", ads->ldap.port); + d_printf("Server time: %s\n", http_timestring(ads->config.current_time)); + + d_printf("KDC server: %s\n", ads->auth.kdc_server ); + d_printf("Server time offset: %d\n", ads->auth.time_offset ); + + return 0; +} + +static void use_in_memory_ccache(void) { + /* Use in-memory credentials cache so we do not interfere with + * existing credentials */ + setenv(KRB5_ENV_CCNAME, "MEMORY:net_ads", 1); +} + +static ADS_STATUS ads_startup_int(struct net_context *c, bool only_own_domain, + uint32 auth_flags, ADS_STRUCT **ads_ret) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS status; + bool need_password = false; + bool second_time = false; + char *cp; + const char *realm = NULL; + bool tried_closest_dc = false; + + /* lp_realm() should be handled by a command line param, + However, the join requires that realm be set in smb.conf + and compares our realm with the remote server's so this is + ok until someone needs more flexibility */ + + *ads_ret = NULL; + +retry_connect: + if (only_own_domain) { + realm = lp_realm(); + } else { + realm = assume_own_realm(c); + } + + ads = ads_init(realm, c->opt_target_workgroup, c->opt_host); + + if (!c->opt_user_name) { + c->opt_user_name = "administrator"; + } + + if (c->opt_user_specified) { + need_password = true; + } + +retry: + if (!c->opt_password && need_password && !c->opt_machine_pass) { + c->opt_password = net_prompt_pass(c, c->opt_user_name); + if (!c->opt_password) { + ads_destroy(&ads); + return ADS_ERROR(LDAP_NO_MEMORY); + } + } + + if (c->opt_password) { + use_in_memory_ccache(); + SAFE_FREE(ads->auth.password); + ads->auth.password = smb_xstrdup(c->opt_password); + } + + ads->auth.flags |= auth_flags; + SAFE_FREE(ads->auth.user_name); + ads->auth.user_name = smb_xstrdup(c->opt_user_name); + + /* + * If the username is of the form "name@realm", + * extract the realm and convert to upper case. + * This is only used to establish the connection. + */ + if ((cp = strchr_m(ads->auth.user_name, '@'))!=0) { + *cp++ = '\0'; + SAFE_FREE(ads->auth.realm); + ads->auth.realm = smb_xstrdup(cp); + strupper_m(ads->auth.realm); + } + + status = ads_connect(ads); + + if (!ADS_ERR_OK(status)) { + + if (NT_STATUS_EQUAL(ads_ntstatus(status), + NT_STATUS_NO_LOGON_SERVERS)) { + DEBUG(0,("ads_connect: %s\n", ads_errstr(status))); + ads_destroy(&ads); + return status; + } + + if (!need_password && !second_time && !(auth_flags & ADS_AUTH_NO_BIND)) { + need_password = true; + second_time = true; + goto retry; + } else { + ads_destroy(&ads); + return status; + } + } + + /* when contacting our own domain, make sure we use the closest DC. + * This is done by reconnecting to ADS because only the first call to + * ads_connect will give us our own sitename */ + + if ((only_own_domain || !c->opt_host) && !tried_closest_dc) { + + tried_closest_dc = true; /* avoid loop */ + + if (!ads->config.tried_closest_dc) { + + namecache_delete(ads->server.realm, 0x1C); + namecache_delete(ads->server.workgroup, 0x1C); + + ads_destroy(&ads); + ads = NULL; + + goto retry_connect; + } + } + + *ads_ret = ads; + return status; +} + +ADS_STATUS ads_startup(struct net_context *c, bool only_own_domain, ADS_STRUCT **ads) +{ + return ads_startup_int(c, only_own_domain, 0, ads); +} + +ADS_STATUS ads_startup_nobind(struct net_context *c, bool only_own_domain, ADS_STRUCT **ads) +{ + return ads_startup_int(c, only_own_domain, ADS_AUTH_NO_BIND, ads); +} + +/* + Check to see if connection can be made via ads. + ads_startup() stores the password in opt_password if it needs to so + that rpc or rap can use it without re-prompting. +*/ +static int net_ads_check_int(const char *realm, const char *workgroup, const char *host) +{ + ADS_STRUCT *ads; + ADS_STATUS status; + + if ( (ads = ads_init( realm, workgroup, host )) == 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; +} + +int net_ads_check_our_domain(struct net_context *c) +{ + return net_ads_check_int(lp_realm(), lp_workgroup(), NULL); +} + +int net_ads_check(struct net_context *c) +{ + return net_ads_check_int(NULL, c->opt_workgroup, c->opt_host); +} + +/* + determine the netbios workgroup name for a domain + */ +static int net_ads_workgroup(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + char addr[INET6_ADDRSTRLEN]; + struct nbt_cldap_netlogon_5 reply; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads workgroup\n" + " Print the workgroup name\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup_nobind(c, false, &ads))) { + d_fprintf(stderr, "Didn't find the cldap server!\n"); + return -1; + } + + if (!ads->config.realm) { + ads->config.realm = CONST_DISCARD(char *, c->opt_target_workgroup); + ads->ldap.port = 389; + } + + print_sockaddr(addr, sizeof(addr), &ads->ldap.ss); + if ( !ads_cldap_netlogon_5(talloc_tos(), addr, ads->server.realm, &reply ) ) { + d_fprintf(stderr, "CLDAP query failed!\n"); + return -1; + } + + d_printf("Workgroup: %s\n", reply.domain); + + ads_destroy(&ads); + + return 0; +} + + + +static bool usergrp_display(ADS_STRUCT *ads, char *field, void **values, void *data_area) +{ + char **disp_fields = (char **) data_area; + + if (!field) { /* must be end of record */ + if (disp_fields[0]) { + if (!strchr_m(disp_fields[0], '$')) { + if (disp_fields[1]) + d_printf("%-21.21s %s\n", + disp_fields[0], disp_fields[1]); + else + d_printf("%s\n", disp_fields[0]); + } + } + SAFE_FREE(disp_fields[0]); + SAFE_FREE(disp_fields[1]); + return true; + } + if (!values) /* must be new field, indicate string field */ + return true; + if (StrCaseCmp(field, "sAMAccountName") == 0) { + disp_fields[0] = SMB_STRDUP((char *) values[0]); + } + if (StrCaseCmp(field, "description") == 0) + disp_fields[1] = SMB_STRDUP((char *) values[0]); + return true; +} + +static int net_ads_user_usage(struct net_context *c, int argc, const char **argv) +{ + return net_user_usage(c, argc, argv); +} + +static int ads_user_add(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS status; + char *upn, *userdn; + LDAPMessage *res=NULL; + int rc = -1; + char *ou_str = NULL; + + if (argc < 1 || c->display_usage) + return net_ads_user_usage(c, argc, argv); + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + status = ads_find_user_acct(ads, &res, argv[0]); + + if (!ADS_ERR_OK(status)) { + d_fprintf(stderr, "ads_user_add: %s\n", ads_errstr(status)); + goto done; + } + + if (ads_count_replies(ads, res)) { + d_fprintf(stderr, "ads_user_add: User %s already exists\n", argv[0]); + goto done; + } + + if (c->opt_container) { + ou_str = SMB_STRDUP(c->opt_container); + } else { + ou_str = ads_default_ou_string(ads, WELL_KNOWN_GUID_USERS); + } + + status = ads_add_user_acct(ads, argv[0], ou_str, c->opt_comment); + + if (!ADS_ERR_OK(status)) { + d_fprintf(stderr, "Could not add user %s: %s\n", argv[0], + ads_errstr(status)); + goto done; + } + + /* if no password is to be set, we're done */ + if (argc == 1) { + d_printf("User %s added\n", argv[0]); + rc = 0; + goto done; + } + + /* try setting the password */ + asprintf(&upn, "%s@%s", argv[0], ads->config.realm); + status = ads_krb5_set_password(ads->auth.kdc_server, upn, argv[1], + ads->auth.time_offset); + safe_free(upn); + if (ADS_ERR_OK(status)) { + d_printf("User %s added\n", argv[0]); + rc = 0; + goto done; + } + + /* password didn't set, delete account */ + d_fprintf(stderr, "Could not add user %s. Error setting password %s\n", + argv[0], ads_errstr(status)); + ads_msgfree(ads, res); + status=ads_find_user_acct(ads, &res, argv[0]); + if (ADS_ERR_OK(status)) { + userdn = ads_get_dn(ads, res); + ads_del_dn(ads, userdn); + ads_memfree(ads, userdn); + } + + done: + if (res) + ads_msgfree(ads, res); + ads_destroy(&ads); + SAFE_FREE(ou_str); + return rc; +} + +static int ads_user_info(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + LDAPMessage *res; + const char *attrs[] = {"memberOf", NULL}; + char *searchstring=NULL; + char **grouplist; + char *escaped_user; + + if (argc < 1 || c->display_usage) { + return net_ads_user_usage(c, argc, argv); + } + + escaped_user = escape_ldap_string_alloc(argv[0]); + + if (!escaped_user) { + d_fprintf(stderr, "ads_user_info: failed to escape user %s\n", argv[0]); + return -1; + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + SAFE_FREE(escaped_user); + return -1; + } + + asprintf(&searchstring, "(sAMAccountName=%s)", escaped_user); + rc = ads_search(ads, &res, searchstring, attrs); + safe_free(searchstring); + + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_search: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + SAFE_FREE(escaped_user); + return -1; + } + + grouplist = ldap_get_values((LDAP *)ads->ldap.ld, + (LDAPMessage *)res, "memberOf"); + + if (grouplist) { + int i; + char **groupname; + for (i=0;grouplist[i];i++) { + groupname = ldap_explode_dn(grouplist[i], 1); + d_printf("%s\n", groupname[0]); + ldap_value_free(groupname); + } + ldap_value_free(grouplist); + } + + ads_msgfree(ads, res); + ads_destroy(&ads); + SAFE_FREE(escaped_user); + return 0; +} + +static int ads_user_delete(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + LDAPMessage *res = NULL; + char *userdn; + + if (argc < 1) { + return net_ads_user_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + rc = ads_find_user_acct(ads, &res, argv[0]); + if (!ADS_ERR_OK(rc) || ads_count_replies(ads, res) != 1) { + d_printf("User %s does not exist.\n", argv[0]); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + userdn = ads_get_dn(ads, res); + ads_msgfree(ads, res); + rc = ads_del_dn(ads, userdn); + ads_memfree(ads, userdn); + if (ADS_ERR_OK(rc)) { + d_printf("User %s deleted\n", argv[0]); + ads_destroy(&ads); + return 0; + } + d_fprintf(stderr, "Error deleting user %s: %s\n", argv[0], + ads_errstr(rc)); + ads_destroy(&ads); + return -1; +} + +int net_ads_user(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "add", + ads_user_add, + NET_TRANSPORT_ADS, + "Add an AD user", + "net ads user add\n" + " Add an AD user" + }, + { + "info", + ads_user_info, + NET_TRANSPORT_ADS, + "Display information about an AD user", + "net ads user info\n" + " Display information about an AD user" + }, + { + "delete", + ads_user_delete, + NET_TRANSPORT_ADS, + "Delete an AD user", + "net ads user delete\n" + " Delete an AD user" + }, + {NULL, NULL, 0, NULL, NULL} + }; + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *shortattrs[] = {"sAMAccountName", NULL}; + const char *longattrs[] = {"sAMAccountName", "description", NULL}; + char *disp_fields[2] = {NULL, NULL}; + + if (argc == 0) { + if (c->display_usage) { + d_printf("Usage:\n"); + d_printf("net ads user\n" + " List AD users\n"); + net_display_usage_from_functable(func); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + if (c->opt_long_list_entries) + d_printf("\nUser name Comment" + "\n-----------------------------\n"); + + rc = ads_do_search_all_fn(ads, ads->config.bind_path, + LDAP_SCOPE_SUBTREE, + "(objectCategory=user)", + c->opt_long_list_entries ? longattrs : + shortattrs, usergrp_display, + disp_fields); + ads_destroy(&ads); + return ADS_ERR_OK(rc) ? 0 : -1; + } + + return net_run_function(c, argc, argv, "net ads user", func); +} + +static int net_ads_group_usage(struct net_context *c, int argc, const char **argv) +{ + return net_group_usage(c, argc, argv); +} + +static int ads_group_add(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS status; + LDAPMessage *res=NULL; + int rc = -1; + char *ou_str = NULL; + + if (argc < 1 || c->display_usage) { + return net_ads_group_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + status = ads_find_user_acct(ads, &res, argv[0]); + + if (!ADS_ERR_OK(status)) { + d_fprintf(stderr, "ads_group_add: %s\n", ads_errstr(status)); + goto done; + } + + if (ads_count_replies(ads, res)) { + d_fprintf(stderr, "ads_group_add: Group %s already exists\n", argv[0]); + goto done; + } + + if (c->opt_container) { + ou_str = SMB_STRDUP(c->opt_container); + } else { + ou_str = ads_default_ou_string(ads, WELL_KNOWN_GUID_USERS); + } + + status = ads_add_group_acct(ads, argv[0], ou_str, c->opt_comment); + + if (ADS_ERR_OK(status)) { + d_printf("Group %s added\n", argv[0]); + rc = 0; + } else { + d_fprintf(stderr, "Could not add group %s: %s\n", argv[0], + ads_errstr(status)); + } + + done: + if (res) + ads_msgfree(ads, res); + ads_destroy(&ads); + SAFE_FREE(ou_str); + return rc; +} + +static int ads_group_delete(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + LDAPMessage *res = NULL; + char *groupdn; + + if (argc < 1 || c->display_usage) { + return net_ads_group_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + rc = ads_find_user_acct(ads, &res, argv[0]); + if (!ADS_ERR_OK(rc) || ads_count_replies(ads, res) != 1) { + d_printf("Group %s does not exist.\n", argv[0]); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + groupdn = ads_get_dn(ads, res); + ads_msgfree(ads, res); + rc = ads_del_dn(ads, groupdn); + ads_memfree(ads, groupdn); + if (ADS_ERR_OK(rc)) { + d_printf("Group %s deleted\n", argv[0]); + ads_destroy(&ads); + return 0; + } + d_fprintf(stderr, "Error deleting group %s: %s\n", argv[0], + ads_errstr(rc)); + ads_destroy(&ads); + return -1; +} + +int net_ads_group(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "add", + ads_group_add, + NET_TRANSPORT_ADS, + "Add an AD group", + "net ads group add\n" + " Add an AD group" + }, + { + "delete", + ads_group_delete, + NET_TRANSPORT_ADS, + "Delete an AD group", + "net ads group delete\n" + " Delete an AD group" + }, + {NULL, NULL, 0, NULL, NULL} + }; + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *shortattrs[] = {"sAMAccountName", NULL}; + const char *longattrs[] = {"sAMAccountName", "description", NULL}; + char *disp_fields[2] = {NULL, NULL}; + + if (argc == 0) { + if (c->display_usage) { + d_printf("Usage:\n"); + d_printf("net ads group\n" + " List AD groups\n"); + net_display_usage_from_functable(func); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + if (c->opt_long_list_entries) + d_printf("\nGroup name Comment" + "\n-----------------------------\n"); + rc = ads_do_search_all_fn(ads, ads->config.bind_path, + LDAP_SCOPE_SUBTREE, + "(objectCategory=group)", + c->opt_long_list_entries ? longattrs : + shortattrs, usergrp_display, + disp_fields); + + ads_destroy(&ads); + return ADS_ERR_OK(rc) ? 0 : -1; + } + return net_run_function(c, argc, argv, "net ads group", func); +} + +static int net_ads_status(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + LDAPMessage *res; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads status\n" + " Display machine account details\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + + rc = ads_find_machine_acct(ads, &res, global_myname()); + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_find_machine_acct: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + return -1; + } + + if (ads_count_replies(ads, res) == 0) { + d_fprintf(stderr, "No machine account for '%s' found\n", global_myname()); + ads_destroy(&ads); + return -1; + } + + ads_dump(ads, res); + ads_destroy(&ads); + return 0; +} + +/******************************************************************* + Leave an AD domain. Windows XP disables the machine account. + We'll try the same. The old code would do an LDAP delete. + That only worked using the machine creds because added the machine + with full control to the computer object's ACL. +*******************************************************************/ + +static int net_ads_leave(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *ctx; + struct libnet_UnjoinCtx *r = NULL; + WERROR werr; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads leave\n" + " Leave an AD domain\n"); + return 0; + } + + if (!*lp_realm()) { + d_fprintf(stderr, "No realm set, are we joined ?\n"); + return -1; + } + + if (!(ctx = talloc_init("net_ads_leave"))) { + d_fprintf(stderr, "Could not initialise talloc context.\n"); + return -1; + } + + if (!c->opt_kerberos) { + use_in_memory_ccache(); + } + + werr = libnet_init_UnjoinCtx(ctx, &r); + if (!W_ERROR_IS_OK(werr)) { + d_fprintf(stderr, "Could not initialise unjoin context.\n"); + return -1; + } + + r->in.debug = true; + r->in.use_kerberos = c->opt_kerberos; + r->in.dc_name = c->opt_host; + r->in.domain_name = lp_realm(); + r->in.admin_account = c->opt_user_name; + r->in.admin_password = net_prompt_pass(c, c->opt_user_name); + r->in.modify_config = lp_config_backend_is_registry(); + r->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE | + WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE; + + werr = libnet_Unjoin(ctx, r); + if (!W_ERROR_IS_OK(werr)) { + d_printf("Failed to leave domain: %s\n", + r->out.error_string ? r->out.error_string : + get_friendly_werror_msg(werr)); + goto done; + } + + if (W_ERROR_IS_OK(werr)) { + d_printf("Deleted account for '%s' in realm '%s'\n", + r->in.machine_name, r->out.dns_domain_name); + goto done; + } + + /* We couldn't delete it - see if the disable succeeded. */ + if (r->out.disabled_machine_account) { + d_printf("Disabled account for '%s' in realm '%s'\n", + r->in.machine_name, r->out.dns_domain_name); + werr = WERR_OK; + goto done; + } + + d_fprintf(stderr, "Failed to disable machine account for '%s' in realm '%s'\n", + r->in.machine_name, r->out.dns_domain_name); + + done: + TALLOC_FREE(r); + TALLOC_FREE(ctx); + + if (W_ERROR_IS_OK(werr)) { + return 0; + } + + return -1; +} + +static NTSTATUS net_ads_join_ok(struct net_context *c) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS status; + + if (!secrets_init()) { + DEBUG(1,("Failed to initialise secrets database\n")); + return NT_STATUS_ACCESS_DENIED; + } + + net_use_krb_machine_account(c); + + status = ads_startup(c, true, &ads); + if (!ADS_ERR_OK(status)) { + return ads_ntstatus(status); + } + + ads_destroy(&ads); + return NT_STATUS_OK; +} + +/* + check that an existing join is OK + */ +int net_ads_testjoin(struct net_context *c, int argc, const char **argv) +{ + NTSTATUS status; + use_in_memory_ccache(); + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads testjoin\n" + " Test if the existing join is ok\n"); + return 0; + } + + /* Display success or failure */ + status = net_ads_join_ok(c); + if (!NT_STATUS_IS_OK(status)) { + fprintf(stderr,"Join to domain is not valid: %s\n", + get_friendly_nt_error_msg(status)); + return -1; + } + + printf("Join is OK\n"); + return 0; +} + +/******************************************************************* + Simple configu checks before beginning the join + ********************************************************************/ + +static WERROR check_ads_config( void ) +{ + if (lp_server_role() != ROLE_DOMAIN_MEMBER ) { + d_printf("Host is not configured as a member server.\n"); + return WERR_INVALID_DOMAIN_ROLE; + } + + if (strlen(global_myname()) > 15) { + d_printf("Our netbios name can be at most 15 chars long, " + "\"%s\" is %u chars long\n", global_myname(), + (unsigned int)strlen(global_myname())); + return WERR_INVALID_COMPUTER_NAME; + } + + if ( lp_security() == SEC_ADS && !*lp_realm()) { + d_fprintf(stderr, "realm must be set in in %s for ADS " + "join to succeed.\n", get_dyn_CONFIGFILE()); + return WERR_INVALID_PARAM; + } + + return WERR_OK; +} + +/******************************************************************* + Send a DNS update request +*******************************************************************/ + +#if defined(WITH_DNS_UPDATES) +#include "dns.h" +DNS_ERROR DoDNSUpdate(char *pszServerName, + const char *pszDomainName, const char *pszHostName, + const struct sockaddr_storage *sslist, + size_t num_addrs ); + +static NTSTATUS net_update_dns_internal(TALLOC_CTX *ctx, ADS_STRUCT *ads, + const char *machine_name, + const struct sockaddr_storage *addrs, + int num_addrs) +{ + struct dns_rr_ns *nameservers = NULL; + int ns_count = 0; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + DNS_ERROR dns_err; + fstring dns_server; + const char *dnsdomain = NULL; + char *root_domain = NULL; + + if ( (dnsdomain = strchr_m( machine_name, '.')) == NULL ) { + d_printf("No DNS domain configured for %s. " + "Unable to perform DNS Update.\n", machine_name); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + dnsdomain++; + + status = ads_dns_lookup_ns( ctx, dnsdomain, &nameservers, &ns_count ); + if ( !NT_STATUS_IS_OK(status) || (ns_count == 0)) { + /* Child domains often do not have NS records. Look + for the NS record for the forest root domain + (rootDomainNamingContext in therootDSE) */ + + const char *rootname_attrs[] = { "rootDomainNamingContext", NULL }; + LDAPMessage *msg = NULL; + char *root_dn; + ADS_STATUS ads_status; + + if ( !ads->ldap.ld ) { + ads_status = ads_connect( ads ); + if ( !ADS_ERR_OK(ads_status) ) { + DEBUG(0,("net_update_dns_internal: Failed to connect to our DC!\n")); + goto done; + } + } + + ads_status = ads_do_search(ads, "", LDAP_SCOPE_BASE, + "(objectclass=*)", rootname_attrs, &msg); + if (!ADS_ERR_OK(ads_status)) { + goto done; + } + + root_dn = ads_pull_string(ads, ctx, msg, "rootDomainNamingContext"); + if ( !root_dn ) { + ads_msgfree( ads, msg ); + goto done; + } + + root_domain = ads_build_domain( root_dn ); + + /* cleanup */ + ads_msgfree( ads, msg ); + + /* try again for NS servers */ + + status = ads_dns_lookup_ns( ctx, root_domain, &nameservers, &ns_count ); + + if ( !NT_STATUS_IS_OK(status) || (ns_count == 0)) { + DEBUG(3,("net_ads_join: Failed to find name server for the %s " + "realm\n", ads->config.realm)); + goto done; + } + + dnsdomain = root_domain; + + } + + /* Now perform the dns update - we'll try non-secure and if we fail, + we'll follow it up with a secure update */ + + fstrcpy( dns_server, nameservers[0].hostname ); + + dns_err = DoDNSUpdate(dns_server, dnsdomain, machine_name, addrs, num_addrs); + if (!ERR_DNS_IS_OK(dns_err)) { + status = NT_STATUS_UNSUCCESSFUL; + } + +done: + + SAFE_FREE( root_domain ); + + return status; +} + +static NTSTATUS net_update_dns(TALLOC_CTX *mem_ctx, ADS_STRUCT *ads) +{ + int num_addrs; + struct sockaddr_storage *iplist = NULL; + fstring machine_name; + NTSTATUS status; + + name_to_fqdn( machine_name, global_myname() ); + strlower_m( machine_name ); + + /* Get our ip address (not the 127.0.0.x address but a real ip + * address) */ + + num_addrs = get_my_ip_address( &iplist ); + if ( num_addrs <= 0 ) { + DEBUG(4,("net_update_dns: Failed to find my non-loopback IP " + "addresses!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + status = net_update_dns_internal(mem_ctx, ads, machine_name, + iplist, num_addrs); + SAFE_FREE( iplist ); + return status; +} +#endif + + +/******************************************************************* + ********************************************************************/ + +static int net_ads_join_usage(struct net_context *c, int argc, const char **argv) +{ + d_printf("net ads join [options]\n"); + d_printf("Valid options:\n"); + d_printf(" createupn[=UPN] Set the userPrincipalName attribute during the join.\n"); + d_printf(" The deault UPN is in the form host/netbiosname@REALM.\n"); + d_printf(" createcomputer=OU Precreate the computer account in a specific OU.\n"); + d_printf(" The OU string read from top to bottom without RDNs and delimited by a '/'.\n"); + d_printf(" E.g. \"createcomputer=Computers/Servers/Unix\"\n"); + d_printf(" NB: A backslash '\\' is used as escape at multiple levels and may\n"); + d_printf(" need to be doubled or even quadrupled. It is not used as a separator.\n"); + d_printf(" osName=string Set the operatingSystem attribute during the join.\n"); + d_printf(" osVer=string Set the operatingSystemVersion attribute during the join.\n"); + d_printf(" NB: osName and osVer must be specified together for either to take effect.\n"); + d_printf(" Also, the operatingSystemService attribute is also set when along with\n"); + d_printf(" the two other attributes.\n"); + + return -1; +} + +/******************************************************************* + ********************************************************************/ + +int net_ads_join(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *ctx = NULL; + struct libnet_JoinCtx *r = NULL; + const char *domain = lp_realm(); + WERROR werr = WERR_SETUP_NOT_JOINED; + bool createupn = false; + const char *machineupn = NULL; + const char *create_in_ou = NULL; + int i; + const char *os_name = NULL; + const char *os_version = NULL; + bool modify_config = lp_config_backend_is_registry(); + + if (c->display_usage) + return net_ads_join_usage(c, argc, argv); + + if (!modify_config) { + + werr = check_ads_config(); + if (!W_ERROR_IS_OK(werr)) { + d_fprintf(stderr, "Invalid configuration. Exiting....\n"); + goto fail; + } + } + + if (!(ctx = talloc_init("net_ads_join"))) { + d_fprintf(stderr, "Could not initialise talloc context.\n"); + werr = WERR_NOMEM; + goto fail; + } + + if (!c->opt_kerberos) { + use_in_memory_ccache(); + } + + werr = libnet_init_JoinCtx(ctx, &r); + if (!W_ERROR_IS_OK(werr)) { + goto fail; + } + + /* process additional command line args */ + + for ( i=0; i<argc; i++ ) { + if ( !StrnCaseCmp(argv[i], "createupn", strlen("createupn")) ) { + createupn = true; + machineupn = get_string_param(argv[i]); + } + else if ( !StrnCaseCmp(argv[i], "createcomputer", strlen("createcomputer")) ) { + if ( (create_in_ou = get_string_param(argv[i])) == NULL ) { + d_fprintf(stderr, "Please supply a valid OU path.\n"); + werr = WERR_INVALID_PARAM; + goto fail; + } + } + else if ( !StrnCaseCmp(argv[i], "osName", strlen("osName")) ) { + if ( (os_name = get_string_param(argv[i])) == NULL ) { + d_fprintf(stderr, "Please supply a operating system name.\n"); + werr = WERR_INVALID_PARAM; + goto fail; + } + } + else if ( !StrnCaseCmp(argv[i], "osVer", strlen("osVer")) ) { + if ( (os_version = get_string_param(argv[i])) == NULL ) { + d_fprintf(stderr, "Please supply a valid operating system version.\n"); + werr = WERR_INVALID_PARAM; + goto fail; + } + } + else { + domain = argv[i]; + } + } + + if (!*domain) { + d_fprintf(stderr, "Please supply a valid domain name\n"); + werr = WERR_INVALID_PARAM; + goto fail; + } + + /* Do the domain join here */ + + r->in.domain_name = domain; + r->in.create_upn = createupn; + r->in.upn = machineupn; + r->in.account_ou = create_in_ou; + r->in.os_name = os_name; + r->in.os_version = os_version; + r->in.dc_name = c->opt_host; + r->in.admin_account = c->opt_user_name; + r->in.admin_password = net_prompt_pass(c, c->opt_user_name); + r->in.debug = true; + r->in.use_kerberos = c->opt_kerberos; + r->in.modify_config = modify_config; + r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE | + WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE | + WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED; + + werr = libnet_Join(ctx, r); + if (!W_ERROR_IS_OK(werr)) { + goto fail; + } + + /* Check the short name of the domain */ + + if (!modify_config && !strequal(lp_workgroup(), r->out.netbios_domain_name)) { + d_printf("The workgroup in %s does not match the short\n", get_dyn_CONFIGFILE()); + d_printf("domain name obtained from the server.\n"); + d_printf("Using the name [%s] from the server.\n", r->out.netbios_domain_name); + d_printf("You should set \"workgroup = %s\" in %s.\n", + r->out.netbios_domain_name, get_dyn_CONFIGFILE()); + } + + d_printf("Using short domain name -- %s\n", r->out.netbios_domain_name); + + if (r->out.dns_domain_name) { + d_printf("Joined '%s' to realm '%s'\n", r->in.machine_name, + r->out.dns_domain_name); + } else { + d_printf("Joined '%s' to domain '%s'\n", r->in.machine_name, + r->out.netbios_domain_name); + } + +#if defined(WITH_DNS_UPDATES) + if (r->out.domain_is_ad) { + /* We enter this block with user creds */ + ADS_STRUCT *ads_dns = NULL; + + if ( (ads_dns = ads_init( lp_realm(), NULL, NULL )) != NULL ) { + /* kinit with the machine password */ + + use_in_memory_ccache(); + asprintf( &ads_dns->auth.user_name, "%s$", global_myname() ); + ads_dns->auth.password = secrets_fetch_machine_password( + r->out.netbios_domain_name, NULL, NULL ); + ads_dns->auth.realm = SMB_STRDUP( r->out.dns_domain_name ); + strupper_m(ads_dns->auth.realm ); + ads_kinit_password( ads_dns ); + } + + if ( !ads_dns || !NT_STATUS_IS_OK(net_update_dns( ctx, ads_dns )) ) { + d_fprintf( stderr, "DNS update failed!\n" ); + } + + /* exit from this block using machine creds */ + ads_destroy(&ads_dns); + } +#endif + TALLOC_FREE(r); + TALLOC_FREE( ctx ); + + return 0; + +fail: + /* issue an overall failure message at the end. */ + d_printf("Failed to join domain: %s\n", + r && r->out.error_string ? r->out.error_string : + get_friendly_werror_msg(werr)); + TALLOC_FREE( ctx ); + + return -1; +} + +/******************************************************************* + ********************************************************************/ + +static int net_ads_dns_register(struct net_context *c, int argc, const char **argv) +{ +#if defined(WITH_DNS_UPDATES) + ADS_STRUCT *ads; + ADS_STATUS status; + TALLOC_CTX *ctx; + +#ifdef DEVELOPER + talloc_enable_leak_report(); +#endif + + if (argc > 0 || c->display_usage) { + d_printf("Usage:\n" + "net ads dns register\n" + " Register hostname with DNS\n"); + return -1; + } + + if (!(ctx = talloc_init("net_ads_dns"))) { + d_fprintf(stderr, "Could not initialise talloc context\n"); + return -1; + } + + status = ads_startup(c, true, &ads); + if ( !ADS_ERR_OK(status) ) { + DEBUG(1, ("error on ads_startup: %s\n", ads_errstr(status))); + TALLOC_FREE(ctx); + return -1; + } + + if ( !NT_STATUS_IS_OK(net_update_dns(ctx, ads)) ) { + d_fprintf( stderr, "DNS update failed!\n" ); + ads_destroy( &ads ); + TALLOC_FREE( ctx ); + return -1; + } + + d_fprintf( stderr, "Successfully registered hostname with DNS\n" ); + + ads_destroy(&ads); + TALLOC_FREE( ctx ); + + return 0; +#else + d_fprintf(stderr, "DNS update support not enabled at compile time!\n"); + return -1; +#endif +} + +#if defined(WITH_DNS_UPDATES) +DNS_ERROR do_gethostbyname(const char *server, const char *host); +#endif + +static int net_ads_dns_gethostbyname(struct net_context *c, int argc, const char **argv) +{ +#if defined(WITH_DNS_UPDATES) + DNS_ERROR err; + +#ifdef DEVELOPER + talloc_enable_leak_report(); +#endif + + if (argc != 2 || c->display_usage) { + d_printf("Usage:\n" + "net ads dns gethostbyname <server> <name>\n" + " Look up hostname from the AD\n" + " server\tName server to use\n" + " name\tName to look up\n"); + return -1; + } + + err = do_gethostbyname(argv[0], argv[1]); + + d_printf("do_gethostbyname returned %d\n", ERROR_DNS_V(err)); +#endif + return 0; +} + +static int net_ads_dns(struct net_context *c, int argc, const char *argv[]) +{ + struct functable func[] = { + { + "register", + net_ads_dns_register, + NET_TRANSPORT_ADS, + "Add host dns entry to AD", + "net ads dns register\n" + " Add host dns entry to AD" + }, + { + "gethostbyname", + net_ads_dns_gethostbyname, + NET_TRANSPORT_ADS, + "Look up host", + "net ads dns gethostbyname\n" + " Look up host" + }, + {NULL, NULL, 0, NULL, NULL} + }; + + return net_run_function(c, argc, argv, "net ads dns", func); +} + +/******************************************************************* + ********************************************************************/ + +int net_ads_printer_usage(struct net_context *c, int argc, const char **argv) +{ + d_printf( +"\nnet ads printer search <printer>" +"\n\tsearch for a printer in the directory\n" +"\nnet ads printer info <printer> <server>" +"\n\tlookup info in directory for printer on server" +"\n\t(note: printer defaults to \"*\", server defaults to local)\n" +"\nnet ads printer publish <printername>" +"\n\tpublish printer in directory" +"\n\t(note: printer name is required)\n" +"\nnet ads printer remove <printername>" +"\n\tremove printer from directory" +"\n\t(note: printer name is required)\n"); + return -1; +} + +/******************************************************************* + ********************************************************************/ + +static int net_ads_printer_search(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + LDAPMessage *res = NULL; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads printer search\n" + " List printers in the AD\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + rc = ads_find_printers(ads, &res); + + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_find_printer: %s\n", ads_errstr(rc)); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + if (ads_count_replies(ads, res) == 0) { + d_fprintf(stderr, "No results found\n"); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + ads_dump(ads, res); + ads_msgfree(ads, res); + ads_destroy(&ads); + return 0; +} + +static int net_ads_printer_info(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *servername, *printername; + LDAPMessage *res = NULL; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads printer info [printername [servername]]\n" + " Display printer info from AD\n" + " printername\tPrinter name or wildcard\n" + " servername\tName of the print server\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + if (argc > 0) { + printername = argv[0]; + } else { + printername = "*"; + } + + if (argc > 1) { + servername = argv[1]; + } else { + servername = global_myname(); + } + + rc = ads_find_printer_on_server(ads, &res, printername, servername); + + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "Server '%s' not found: %s\n", + servername, ads_errstr(rc)); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + if (ads_count_replies(ads, res) == 0) { + d_fprintf(stderr, "Printer '%s' not found\n", printername); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + ads_dump(ads, res); + ads_msgfree(ads, res); + ads_destroy(&ads); + + return 0; +} + +static int net_ads_printer_publish(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *servername, *printername; + struct cli_state *cli; + struct rpc_pipe_client *pipe_hnd; + struct sockaddr_storage server_ss; + NTSTATUS nt_status; + TALLOC_CTX *mem_ctx = talloc_init("net_ads_printer_publish"); + ADS_MODLIST mods = ads_init_mods(mem_ctx); + char *prt_dn, *srv_dn, **srv_cn; + char *srv_cn_escaped = NULL, *printername_escaped = NULL; + LDAPMessage *res = NULL; + + if (argc < 1 || c->display_usage) { + d_printf("Usage:\n" + "net ads printer publish <printername> [servername]\n" + " Publish printer in AD\n" + " printername\tName of the printer\n" + " servername\tName of the print server\n"); + talloc_destroy(mem_ctx); + return -1; + } + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + talloc_destroy(mem_ctx); + return -1; + } + + printername = argv[0]; + + if (argc == 2) { + servername = argv[1]; + } else { + servername = global_myname(); + } + + /* Get printer data from SPOOLSS */ + + resolve_name(servername, &server_ss, 0x20); + + nt_status = cli_full_connection(&cli, global_myname(), servername, + &server_ss, 0, + "IPC$", "IPC", + c->opt_user_name, c->opt_workgroup, + c->opt_password ? c->opt_password : "", + CLI_FULL_CONNECTION_USE_KERBEROS, + Undefined, NULL); + + if (NT_STATUS_IS_ERR(nt_status)) { + d_fprintf(stderr, "Unable to open a connnection to %s to obtain data " + "for %s\n", servername, printername); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + /* Publish on AD server */ + + ads_find_machine_acct(ads, &res, servername); + + if (ads_count_replies(ads, res) == 0) { + d_fprintf(stderr, "Could not find machine account for server %s\n", + servername); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + srv_dn = ldap_get_dn((LDAP *)ads->ldap.ld, (LDAPMessage *)res); + srv_cn = ldap_explode_dn(srv_dn, 1); + + srv_cn_escaped = escape_rdn_val_string_alloc(srv_cn[0]); + printername_escaped = escape_rdn_val_string_alloc(printername); + if (!srv_cn_escaped || !printername_escaped) { + SAFE_FREE(srv_cn_escaped); + SAFE_FREE(printername_escaped); + d_fprintf(stderr, "Internal error, out of memory!"); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + asprintf(&prt_dn, "cn=%s-%s,%s", srv_cn_escaped, printername_escaped, srv_dn); + + SAFE_FREE(srv_cn_escaped); + SAFE_FREE(printername_escaped); + + nt_status = cli_rpc_pipe_open_noauth(cli, &syntax_spoolss, &pipe_hnd); + if (!NT_STATUS_IS_OK(nt_status)) { + d_fprintf(stderr, "Unable to open a connnection to the spoolss pipe on %s\n", + servername); + SAFE_FREE(prt_dn); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + if (!W_ERROR_IS_OK(get_remote_printer_publishing_data(pipe_hnd, mem_ctx, &mods, + printername))) { + SAFE_FREE(prt_dn); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + rc = ads_add_printer_entry(ads, prt_dn, mem_ctx, &mods); + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_publish_printer: %s\n", ads_errstr(rc)); + SAFE_FREE(prt_dn); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + return -1; + } + + d_printf("published printer\n"); + SAFE_FREE(prt_dn); + ads_destroy(&ads); + talloc_destroy(mem_ctx); + + return 0; +} + +static int net_ads_printer_remove(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *servername; + char *prt_dn; + LDAPMessage *res = NULL; + + if (argc < 1 || c->display_usage) { + d_printf("Usage:\n" + "net ads printer remove <printername> [servername]\n" + " Remove a printer from the AD\n" + " printername\tName of the printer\n" + " servername\tName of the print server\n"); + return -1; + } + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + + if (argc > 1) { + servername = argv[1]; + } else { + servername = global_myname(); + } + + rc = ads_find_printer_on_server(ads, &res, argv[0], servername); + + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_find_printer_on_server: %s\n", ads_errstr(rc)); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + if (ads_count_replies(ads, res) == 0) { + d_fprintf(stderr, "Printer '%s' not found\n", argv[1]); + ads_msgfree(ads, res); + ads_destroy(&ads); + return -1; + } + + prt_dn = ads_get_dn(ads, res); + ads_msgfree(ads, res); + rc = ads_del_dn(ads, prt_dn); + ads_memfree(ads, prt_dn); + + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "ads_del_dn: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + return -1; + } + + ads_destroy(&ads); + return 0; +} + +static int net_ads_printer(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "search", + net_ads_printer_search, + NET_TRANSPORT_ADS, + "Search for a printer", + "net ads printer search\n" + " Search for a printer" + }, + { + "info", + net_ads_printer_info, + NET_TRANSPORT_ADS, + "Display printer information", + "net ads printer info\n" + " Display printer information" + }, + { + "publish", + net_ads_printer_publish, + NET_TRANSPORT_ADS, + "Publish a printer", + "net ads printer publish\n" + " Publish a printer" + }, + { + "remove", + net_ads_printer_remove, + NET_TRANSPORT_ADS, + "Delete a printer", + "net ads printer remove\n" + " Delete a printer" + }, + {NULL, NULL, 0, NULL, NULL} + }; + + return net_run_function(c, argc, argv, "net ads printer", func); +} + + +static int net_ads_password(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + const char *auth_principal = c->opt_user_name; + const char *auth_password = c->opt_password; + char *realm = NULL; + char *new_password = NULL; + char *chr, *prompt; + const char *user; + ADS_STATUS ret; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads password <username>\n" + " Change password for user\n" + " username\tName of user to change password for\n"); + return 0; + } + + if (c->opt_user_name == NULL || c->opt_password == NULL) { + d_fprintf(stderr, "You must supply an administrator username/password\n"); + return -1; + } + + if (argc < 1) { + d_fprintf(stderr, "ERROR: You must say which username to change password for\n"); + return -1; + } + + user = argv[0]; + if (!strchr_m(user, '@')) { + asprintf(&chr, "%s@%s", argv[0], lp_realm()); + user = chr; + } + + use_in_memory_ccache(); + chr = strchr_m(auth_principal, '@'); + if (chr) { + realm = ++chr; + } else { + realm = lp_realm(); + } + + /* use the realm so we can eventually change passwords for users + in realms other than default */ + if (!(ads = ads_init(realm, c->opt_workgroup, c->opt_host))) { + return -1; + } + + /* we don't actually need a full connect, but it's the easy way to + fill in the KDC's addresss */ + ads_connect(ads); + + if (!ads->config.realm) { + d_fprintf(stderr, "Didn't find the kerberos server!\n"); + return -1; + } + + if (argv[1]) { + new_password = (char *)argv[1]; + } else { + asprintf(&prompt, "Enter new password for %s:", user); + new_password = getpass(prompt); + free(prompt); + } + + ret = kerberos_set_password(ads->auth.kdc_server, auth_principal, + auth_password, user, new_password, ads->auth.time_offset); + if (!ADS_ERR_OK(ret)) { + d_fprintf(stderr, "Password change failed: %s\n", ads_errstr(ret)); + ads_destroy(&ads); + return -1; + } + + d_printf("Password change for %s completed.\n", user); + ads_destroy(&ads); + + return 0; +} + +int net_ads_changetrustpw(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + char *host_principal; + fstring my_name; + ADS_STATUS ret; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads changetrustpw\n" + " Change the machine account's trust password\n"); + return 0; + } + + if (!secrets_init()) { + DEBUG(1,("Failed to initialise secrets database\n")); + return -1; + } + + net_use_krb_machine_account(c); + + use_in_memory_ccache(); + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + + fstrcpy(my_name, global_myname()); + strlower_m(my_name); + asprintf(&host_principal, "%s$@%s", my_name, ads->config.realm); + d_printf("Changing password for principal: %s\n", host_principal); + + ret = ads_change_trust_account_password(ads, host_principal); + + if (!ADS_ERR_OK(ret)) { + d_fprintf(stderr, "Password change failed: %s\n", ads_errstr(ret)); + ads_destroy(&ads); + SAFE_FREE(host_principal); + return -1; + } + + d_printf("Password change for principal %s succeeded.\n", host_principal); + + if (lp_use_kerberos_keytab()) { + d_printf("Attempting to update system keytab with new password.\n"); + if (ads_keytab_create_default(ads)) { + d_printf("Failed to update system keytab.\n"); + } + } + + ads_destroy(&ads); + SAFE_FREE(host_principal); + + return 0; +} + +/* + help for net ads search +*/ +static int net_ads_search_usage(struct net_context *c, int argc, const char **argv) +{ + d_printf( + "\nnet ads search <expression> <attributes...>\n" + "\nPerform a raw LDAP search on a ADS server and dump the results.\n" + "The expression is a standard LDAP search expression, and the\n" + "attributes are a list of LDAP fields to show in the results.\n\n" + "Example: net ads search '(objectCategory=group)' sAMAccountName\n\n" + ); + net_common_flags_usage(c, argc, argv); + return -1; +} + + +/* + general ADS search function. Useful in diagnosing problems in ADS +*/ +static int net_ads_search(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *ldap_exp; + const char **attrs; + LDAPMessage *res = NULL; + + if (argc < 1 || c->display_usage) { + return net_ads_search_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + ldap_exp = argv[0]; + attrs = (argv + 1); + + rc = ads_do_search_all(ads, ads->config.bind_path, + LDAP_SCOPE_SUBTREE, + ldap_exp, attrs, &res); + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "search failed: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + return -1; + } + + d_printf("Got %d replies\n\n", ads_count_replies(ads, res)); + + /* dump the results */ + ads_dump(ads, res); + + ads_msgfree(ads, res); + ads_destroy(&ads); + + return 0; +} + + +/* + help for net ads search +*/ +static int net_ads_dn_usage(struct net_context *c, int argc, const char **argv) +{ + d_printf( + "\nnet ads dn <dn> <attributes...>\n" + "\nperform a raw LDAP search on a ADS server and dump the results\n" + "The DN standard LDAP DN, and the attributes are a list of LDAP fields \n" + "to show in the results\n\n" + "Example: net ads dn 'CN=administrator,CN=Users,DC=my,DC=domain' sAMAccountName\n\n" + "Note: the DN must be provided properly escaped. See RFC 4514 for details\n\n" + ); + net_common_flags_usage(c, argc, argv); + return -1; +} + + +/* + general ADS search function. Useful in diagnosing problems in ADS +*/ +static int net_ads_dn(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *dn; + const char **attrs; + LDAPMessage *res = NULL; + + if (argc < 1 || c->display_usage) { + return net_ads_dn_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + dn = argv[0]; + attrs = (argv + 1); + + rc = ads_do_search_all(ads, dn, + LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, &res); + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "search failed: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + return -1; + } + + d_printf("Got %d replies\n\n", ads_count_replies(ads, res)); + + /* dump the results */ + ads_dump(ads, res); + + ads_msgfree(ads, res); + ads_destroy(&ads); + + return 0; +} + +/* + help for net ads sid search +*/ +static int net_ads_sid_usage(struct net_context *c, int argc, const char **argv) +{ + d_printf( + "\nnet ads sid <sid> <attributes...>\n" + "\nperform a raw LDAP search on a ADS server and dump the results\n" + "The SID is in string format, and the attributes are a list of LDAP fields \n" + "to show in the results\n\n" + "Example: net ads sid 'S-1-5-32' distinguishedName\n\n" + ); + net_common_flags_usage(c, argc, argv); + return -1; +} + + +/* + general ADS search function. Useful in diagnosing problems in ADS +*/ +static int net_ads_sid(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + ADS_STATUS rc; + const char *sid_string; + const char **attrs; + LDAPMessage *res = NULL; + DOM_SID sid; + + if (argc < 1 || c->display_usage) { + return net_ads_sid_usage(c, argc, argv); + } + + if (!ADS_ERR_OK(ads_startup(c, false, &ads))) { + return -1; + } + + sid_string = argv[0]; + attrs = (argv + 1); + + if (!string_to_sid(&sid, sid_string)) { + d_fprintf(stderr, "could not convert sid\n"); + ads_destroy(&ads); + return -1; + } + + rc = ads_search_retry_sid(ads, &res, &sid, attrs); + if (!ADS_ERR_OK(rc)) { + d_fprintf(stderr, "search failed: %s\n", ads_errstr(rc)); + ads_destroy(&ads); + return -1; + } + + d_printf("Got %d replies\n\n", ads_count_replies(ads, res)); + + /* dump the results */ + ads_dump(ads, res); + + ads_msgfree(ads, res); + ads_destroy(&ads); + + return 0; +} + +static int net_ads_keytab_flush(struct net_context *c, int argc, const char **argv) +{ + int ret; + ADS_STRUCT *ads; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads keytab flush\n" + " Delete the whole keytab\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + ret = ads_keytab_flush(ads); + ads_destroy(&ads); + return ret; +} + +static int net_ads_keytab_add(struct net_context *c, int argc, const char **argv) +{ + int i; + int ret = 0; + ADS_STRUCT *ads; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads keytab add <principal> [principal ...]\n" + " Add principals to local keytab\n" + " principal\tKerberos principal to add to " + "keytab\n"); + return 0; + } + + d_printf("Processing principals to add...\n"); + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + for (i = 0; i < argc; i++) { + ret |= ads_keytab_add_entry(ads, argv[i]); + } + ads_destroy(&ads); + return ret; +} + +static int net_ads_keytab_create(struct net_context *c, int argc, const char **argv) +{ + ADS_STRUCT *ads; + int ret; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads keytab create\n" + " Create new default keytab\n"); + return 0; + } + + if (!ADS_ERR_OK(ads_startup(c, true, &ads))) { + return -1; + } + ret = ads_keytab_create_default(ads); + ads_destroy(&ads); + return ret; +} + +static int net_ads_keytab_list(struct net_context *c, int argc, const char **argv) +{ + const char *keytab = NULL; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads keytab list [keytab]\n" + " List a local keytab\n" + " keytab\tKeytab to list\n"); + return 0; + } + + if (argc >= 1) { + keytab = argv[0]; + } + + return ads_keytab_list(keytab); +} + + +int net_ads_keytab(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "add", + net_ads_keytab_add, + NET_TRANSPORT_ADS, + "Add a service principal", + "net ads keytab add\n" + " Add a service principal" + }, + { + "create", + net_ads_keytab_create, + NET_TRANSPORT_ADS, + "Create a fresh keytab", + "net ads keytab create\n" + " Create a fresh keytab" + }, + { + "flush", + net_ads_keytab_flush, + NET_TRANSPORT_ADS, + "Remove all keytab entries", + "net ads keytab flush\n" + " Remove all keytab entries" + }, + { + "list", + net_ads_keytab_list, + NET_TRANSPORT_ADS, + "List a keytab", + "net ads keytab list\n" + " List a keytab" + }, + {NULL, NULL, 0, NULL, NULL} + }; + + if (!lp_use_kerberos_keytab()) { + d_printf("\nWarning: \"use kerberos keytab\" must be set to \"true\" in order to \ +use keytab functions.\n"); + } + + return net_run_function(c, argc, argv, "net ads keytab", func); +} + +static int net_ads_kerberos_renew(struct net_context *c, int argc, const char **argv) +{ + int ret = -1; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads kerberos renew\n" + " Renew TGT from existing credential cache\n"); + return 0; + } + + ret = smb_krb5_renew_ticket(NULL, NULL, NULL, NULL); + if (ret) { + d_printf("failed to renew kerberos ticket: %s\n", + error_message(ret)); + } + return ret; +} + +static int net_ads_kerberos_pac(struct net_context *c, int argc, const char **argv) +{ + struct PAC_DATA *pac = NULL; + struct PAC_LOGON_INFO *info = NULL; + TALLOC_CTX *mem_ctx = NULL; + NTSTATUS status; + int ret = -1; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads kerberos pac\n" + " Dump the Kerberos PAC\n"); + return 0; + } + + mem_ctx = talloc_init("net_ads_kerberos_pac"); + if (!mem_ctx) { + goto out; + } + + c->opt_password = net_prompt_pass(c, c->opt_user_name); + + status = kerberos_return_pac(mem_ctx, + c->opt_user_name, + c->opt_password, + 0, + NULL, + NULL, + NULL, + true, + true, + 2592000, /* one month */ + &pac); + if (!NT_STATUS_IS_OK(status)) { + d_printf("failed to query kerberos PAC: %s\n", + nt_errstr(status)); + goto out; + } + + info = get_logon_info_from_pac(pac); + if (info) { + const char *s; + s = NDR_PRINT_STRUCT_STRING(mem_ctx, PAC_LOGON_INFO, info); + d_printf("The Pac: %s\n", s); + } + + ret = 0; + out: + TALLOC_FREE(mem_ctx); + return ret; +} + +static int net_ads_kerberos_kinit(struct net_context *c, int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx = NULL; + int ret = -1; + NTSTATUS status; + + if (c->display_usage) { + d_printf("Usage:\n" + "net ads kerberos kinit\n" + " Get Ticket Granting Ticket (TGT) for the user\n"); + return 0; + } + + mem_ctx = talloc_init("net_ads_kerberos_kinit"); + if (!mem_ctx) { + goto out; + } + + c->opt_password = net_prompt_pass(c, c->opt_user_name); + + ret = kerberos_kinit_password_ext(c->opt_user_name, + c->opt_password, + 0, + NULL, + NULL, + NULL, + true, + true, + 2592000, /* one month */ + &status); + if (ret) { + d_printf("failed to kinit password: %s\n", + nt_errstr(status)); + } + out: + return ret; +} + +int net_ads_kerberos(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "kinit", + net_ads_kerberos_kinit, + NET_TRANSPORT_ADS, + "Retrieve Ticket Granting Ticket (TGT)", + "net ads kerberos kinit\n" + " Receive Ticket Granting Ticket (TGT)" + }, + { + "renew", + net_ads_kerberos_renew, + NET_TRANSPORT_ADS, + "Renew Ticket Granting Ticket from credential cache" + "net ads kerberos renew\n" + " Renew Ticket Granting Ticket from credential cache" + }, + { + "pac", + net_ads_kerberos_pac, + NET_TRANSPORT_ADS, + "Dump Kerberos PAC", + "net ads kerberos pac\n" + " Dump Kerberos PAC" + }, + {NULL, NULL, 0, NULL, NULL} + }; + + return net_run_function(c, argc, argv, "net ads kerberos", func); +} + +int net_ads(struct net_context *c, int argc, const char **argv) +{ + struct functable func[] = { + { + "info", + net_ads_info, + NET_TRANSPORT_ADS, + "Display details on remote ADS server", + "net ads info\n" + " Display details on remote ADS server" + }, + { + "join", + net_ads_join, + NET_TRANSPORT_ADS, + "Join the local machine to ADS realm", + "net ads join\n" + " Join the local machine to ADS realm" + }, + { + "testjoin", + net_ads_testjoin, + NET_TRANSPORT_ADS, + "Validate machine account", + "net ads testjoin\n" + " Validate machine account" + }, + { + "leave", + net_ads_leave, + NET_TRANSPORT_ADS, + "Remove the local machine from ADS", + "net ads leave\n" + " Remove the local machine from ADS" + }, + { + "status", + net_ads_status, + NET_TRANSPORT_ADS, + "Display machine account details", + "net ads status\n" + " Display machine account details" + }, + { + "user", + net_ads_user, + NET_TRANSPORT_ADS, + "List/modify users", + "net ads user\n" + " List/modify users" + }, + { + "group", + net_ads_group, + NET_TRANSPORT_ADS, + "List/modify groups", + "net ads group\n" + " List/modify groups" + }, + { + "dns", + net_ads_dns, + NET_TRANSPORT_ADS, + "Issue dynamic DNS update", + "net ads dns\n" + " Issue dynamic DNS update" + }, + { + "password", + net_ads_password, + NET_TRANSPORT_ADS, + "Change user passwords", + "net ads password\n" + " Change user passwords" + }, + { + "changetrustpw", + net_ads_changetrustpw, + NET_TRANSPORT_ADS, + "Change trust account password", + "net ads changetrustpw\n" + " Change trust account password" + }, + { + "printer", + net_ads_printer, + NET_TRANSPORT_ADS, + "List/modify printer entries", + "net ads printer\n" + " List/modify printer entries" + }, + { + "search", + net_ads_search, + NET_TRANSPORT_ADS, + "Issue LDAP search using filter", + "net ads search\n" + " Issue LDAP search using filter" + }, + { + "dn", + net_ads_dn, + NET_TRANSPORT_ADS, + "Issue LDAP search by DN", + "net ads dn\n" + " Issue LDAP search by DN" + }, + { + "sid", + net_ads_sid, + NET_TRANSPORT_ADS, + "Issue LDAP search by SID", + "net ads sid\n" + " Issue LDAP search by SID" + }, + { + "workgroup", + net_ads_workgroup, + NET_TRANSPORT_ADS, + "Display workgroup name", + "net ads workgroup\n" + " Display the workgroup name" + }, + { + "lookup", + net_ads_lookup, + NET_TRANSPORT_ADS, + "Perfom CLDAP query on DC", + "net ads lookup\n" + " Find the ADS DC using CLDAP lookups" + }, + { + "keytab", + net_ads_keytab, + NET_TRANSPORT_ADS, + "Manage local keytab file", + "net ads keytab\n" + " Manage local keytab file" + }, + { + "gpo", + net_ads_gpo, + NET_TRANSPORT_ADS, + "Manage group policy objects", + "net ads gpo\n" + " Manage group policy objects" + }, + { + "kerberos", + net_ads_kerberos, + NET_TRANSPORT_ADS, + "Manage kerberos keytab", + "net ads kerberos\n" + " Manage kerberos keytab" + }, + {NULL, NULL, 0, NULL, NULL} + }; + + return net_run_function(c, argc, argv, "net ads", func); +} + +#else + +static int net_ads_noads(void) +{ + d_fprintf(stderr, "ADS support not compiled in\n"); + return -1; +} + +int net_ads_keytab(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +int net_ads_kerberos(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +int net_ads_changetrustpw(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +int net_ads_join(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +int net_ads_user(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +int net_ads_group(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +/* this one shouldn't display a message */ +int net_ads_check(struct net_context *c) +{ + return -1; +} + +int net_ads_check_our_domain(struct net_context *c) +{ + return -1; +} + +int net_ads(struct net_context *c, int argc, const char **argv) +{ + return net_ads_noads(); +} + +#endif /* WITH_ADS */ |