summaryrefslogtreecommitdiffstats
path: root/source/libads
diff options
context:
space:
mode:
authorGerald Carter <jerry@samba.org>2004-07-07 19:18:31 +0000
committerGerald Carter <jerry@samba.org>2004-07-07 19:18:31 +0000
commit0cf550c32aa0018ad834fc2d3cd90d9b2b146621 (patch)
tree687da224b86274ebc9c46591a812d206935c8efd /source/libads
parent99c6d9bd34b106ae5109ffed4812b89ef0f3eaa8 (diff)
downloadsamba-0cf550c32aa0018ad834fc2d3cd90d9b2b146621.tar.gz
samba-0cf550c32aa0018ad834fc2d3cd90d9b2b146621.tar.xz
samba-0cf550c32aa0018ad834fc2d3cd90d9b2b146621.zip
r1383: sync from 3.0 tree
Diffstat (limited to 'source/libads')
-rw-r--r--source/libads/kerberos.c4
-rw-r--r--source/libads/kerberos_keytab.c554
-rw-r--r--source/libads/kerberos_verify.c313
-rw-r--r--source/libads/krb5_setpw.c49
-rw-r--r--source/libads/ldap.c410
-rw-r--r--source/libads/sasl.c4
6 files changed, 1156 insertions, 178 deletions
diff --git a/source/libads/kerberos.c b/source/libads/kerberos.c
index e8bf4b08462..97b895a2418 100644
--- a/source/libads/kerberos.c
+++ b/source/libads/kerberos.c
@@ -79,9 +79,9 @@ int kerberos_kinit_password(const char *principal, const char *password, int tim
return code;
}
- if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, NULL,
+ if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, password,
kerb_prompter,
- password, 0, NULL, NULL))) {
+ NULL, 0, NULL, NULL))) {
krb5_free_principal(ctx, me);
krb5_free_context(ctx);
return code;
diff --git a/source/libads/kerberos_keytab.c b/source/libads/kerberos_keytab.c
new file mode 100644
index 00000000000..eec5f104fd9
--- /dev/null
+++ b/source/libads/kerberos_keytab.c
@@ -0,0 +1,554 @@
+/*
+ Unix SMB/CIFS implementation.
+ kerberos keytab utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
+ Copyright (C) Guenther Deschner 2003
+ Copyright (C) Rakesh Patel 2004
+ Copyright (C) Dan Perry 2004
+ Copyright (C) Jeremy Allison 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 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.
+*/
+
+#include "includes.h"
+
+#ifdef HAVE_KRB5
+
+/**********************************************************************
+ Adds a single service principal, i.e. 'host' to the system keytab
+***********************************************************************/
+
+int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ krb5_principal princ = NULL;
+ krb5_data password;
+ krb5_enctype *enctypes = NULL;
+ krb5_kvno kvno;
+
+ char *principal = NULL;
+ char *princ_s = NULL;
+ char *password_s = NULL;
+#ifndef MAX_KEYTAB_NAME_LEN
+#define MAX_KEYTAB_NAME_LEN 1100
+#endif
+ char keytab_name[MAX_KEYTAB_NAME_LEN]; /* This MAX_NAME_LEN is a constant defined in krb5.h */
+ fstring my_fqdn;
+ int i;
+ char *ktprinc = NULL;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ initialize_krb5_error_table();
+ ret = krb5_init_context(&context);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: could not krb5_init_context: %s\n",error_message(ret)));
+ return -1;
+ }
+#ifdef HAVE_WRFILE_KEYTAB /* MIT */
+ keytab_name[0] = 'W';
+ keytab_name[1] = 'R';
+ ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
+#else /* Heimdal */
+ ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
+#endif
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_default_name failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ DEBUG(2,("ads_keytab_add_entry: Using default system keytab: %s\n", (char *) &keytab_name));
+ ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_resolve failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ /* retrieve the password */
+ if (!secrets_init()) {
+ DEBUG(1,("ads_keytab_add_entry: secrets_init failed\n"));
+ ret = -1;
+ goto out;
+ }
+ password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+ if (!password_s) {
+ DEBUG(1,("ads_keytab_add_entry: failed to fetch machine password\n"));
+ ret = -1;
+ goto out;
+ }
+ password.data = password_s;
+ password.length = strlen(password_s);
+
+ /* Construct our principal */
+ name_to_fqdn(my_fqdn, global_myname());
+ strlower_m(my_fqdn);
+ asprintf(&princ_s, "%s/%s@%s", srvPrinc, my_fqdn, lp_realm());
+
+ ret = krb5_parse_name(context, princ_s, &princ);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_parse_name(%s) failed (%s)\n", princ_s, error_message(ret)));
+ goto out;
+ }
+
+ kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
+ if (kvno == -1) { /* -1 indicates failure, everything else is OK */
+ DEBUG(1,("ads_keytab_add_entry: ads_get_kvno failed to determine the system's kvno.\n"));
+ ret = -1;
+ goto out;
+ }
+
+ /* Seek and delete old keytab entries */
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret != KRB5_KT_END && ret != ENOENT ) {
+ DEBUG(3,("ads_keytab_add_entry: Will try to delete old keytab entries\n"));
+ while(!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
+ BOOL compare_ok = False;
+
+ ret = krb5_unparse_name(context, kt_entry.principal, &ktprinc);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_unparse_name failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ /*---------------------------------------------------------------------------
+ * Save the entries with kvno - 1. This is what microsoft does
+ * to allow people with existing sessions that have kvno - 1 to still
+ * work. Otherwise, when the password for the machine changes, all
+ * kerberizied sessions will 'break' until either the client reboots or
+ * the client's session key expires and they get a new session ticket
+ * with the new kvno.
+ */
+
+#ifdef HAVE_KRB5_KT_COMPARE
+ compare_ok = ((krb5_kt_compare(context, &kt_entry, princ, 0, 0) == True) && (kt_entry.vno != kvno - 1));
+#else
+ compare_ok = ((strcmp(ktprinc, princ_s) == 0) && (kt_entry.vno != kvno - 1));
+#endif
+ krb5_free_unparsed_name(context, ktprinc);
+ ktprinc = NULL;
+
+ if (compare_ok) {
+ DEBUG(3,("ads_keytab_add_entry: Found old entry for principal: %s (kvno %d) - trying to remove it.\n",
+ princ_s, kt_entry.vno));
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get() failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+ ret = krb5_kt_remove_entry(context, keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_start_seq failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+ ret = smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_remove_entry failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+ continue;
+ }
+
+ /* Not a match, just free this entry and continue. */
+ ret = smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: smb_krb5_kt_free_entry failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ }
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: krb5_kt_end_seq_get failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+ }
+
+ /* Ensure we don't double free. */
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ /* If we get here, we have deleted all the old entries with kvno's not equal to the current kvno-1. */
+
+ ret = get_kerberos_allowed_etypes(context,&enctypes);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: get_kerberos_allowed_etypes failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+
+ /* Now add keytab entries for all encryption types */
+ for (i = 0; enctypes[i]; i++) {
+ krb5_keyblock *keyp;
+
+#if !defined(HAVE_KRB5_KEYTAB_ENTRY_KEY) && !defined(HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK)
+#error krb5_keytab_entry has no key or keyblock member
+#endif
+#ifdef HAVE_KRB5_KEYTAB_ENTRY_KEY /* MIT */
+ keyp = &kt_entry.key;
+#endif
+#ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK /* Heimdal */
+ keyp = &kt_entry.keyblock;
+#endif
+ if (create_kerberos_key_from_string(context, princ, &password, keyp, enctypes[i])) {
+ continue;
+ }
+
+ kt_entry.principal = princ;
+ kt_entry.vno = kvno;
+
+ DEBUG(3,("ads_keytab_add_entry: adding keytab entry for (%s) with encryption type (%d) and version (%d)\n",
+ princ_s, enctypes[i], kt_entry.vno));
+ ret = krb5_kt_add_entry(context, keytab, &kt_entry);
+ krb5_free_keyblock_contents(context, keyp);
+ ZERO_STRUCT(kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_add_entry: adding entry to keytab failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ }
+
+ krb5_kt_close(context, keytab);
+ keytab = NULL; /* Done with keytab now. No double free. */
+
+ /* Update the LDAP with the SPN */
+ DEBUG(3,("ads_keytab_add_entry: Attempting to add/update '%s'\n", princ_s));
+ if (!ADS_ERR_OK(ads_add_service_principal_name(ads, global_myname(), srvPrinc))) {
+ DEBUG(1,("ads_keytab_add_entry: ads_add_service_principcal_name failed.\n"));
+ goto out;
+ }
+
+out:
+
+ SAFE_FREE(principal);
+ SAFE_FREE(password_s);
+ SAFE_FREE(princ_s);
+
+ {
+ krb5_keytab_entry zero_kt_entry;
+ ZERO_STRUCT(zero_kt_entry);
+ if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ }
+ }
+ if (princ) {
+ krb5_free_principal(context, princ);
+ }
+ if (enctypes) {
+ free_kerberos_etypes(context, enctypes);
+ }
+
+ {
+ krb5_kt_cursor zero_csr;
+ ZERO_STRUCT(zero_csr);
+ if ((memcmp(&cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return (int)ret;
+}
+
+/**********************************************************************
+ Flushes all entries from the system keytab.
+***********************************************************************/
+
+int ads_keytab_flush(ADS_STRUCT *ads)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ krb5_kvno kvno;
+ char keytab_name[MAX_KEYTAB_NAME_LEN];
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ initialize_krb5_error_table();
+ ret = krb5_init_context(&context);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: could not krb5_init_context: %s\n",error_message(ret)));
+ return ret;
+ }
+#ifdef HAVE_WRFILE_KEYTAB
+ keytab_name[0] = 'W';
+ keytab_name[1] = 'R';
+ ret = krb5_kt_default_name(context, (char *) &keytab_name[2], MAX_KEYTAB_NAME_LEN - 4);
+#else
+ ret = krb5_kt_default_name(context, (char *) &keytab_name[0], MAX_KEYTAB_NAME_LEN - 2);
+#endif
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ DEBUG(3,("ads_keytab_flush: Using default keytab: %s\n", (char *) &keytab_name));
+ ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ ret = krb5_kt_resolve(context, (char *) &keytab_name, &keytab);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_default failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
+ if (kvno == -1) { /* -1 indicates a failure */
+ DEBUG(1,("ads_keytab_flush: Error determining the system's kvno.\n"));
+ goto out;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret != KRB5_KT_END && ret != ENOENT) {
+ while (!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_end_seq_get() failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+ ret = krb5_kt_remove_entry(context, keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_start_seq failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+ ret = smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ if (ret) {
+ DEBUG(1,("ads_keytab_flush: krb5_kt_remove_entry failed (%s)\n",error_message(ret)));
+ goto out;
+ }
+ }
+ }
+
+ /* Ensure we don't double free. */
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ if (!ADS_ERR_OK(ads_clear_service_principal_names(ads, global_myname()))) {
+ DEBUG(1,("ads_keytab_flush: Error while clearing service principal listings in LDAP.\n"));
+ goto out;
+ }
+
+out:
+
+ {
+ krb5_keytab_entry zero_kt_entry;
+ ZERO_STRUCT(zero_kt_entry);
+ if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ }
+ }
+ {
+ krb5_kt_cursor zero_csr;
+ ZERO_STRUCT(zero_csr);
+ if ((memcmp(&cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return ret;
+}
+
+/**********************************************************************
+ Adds all the required service principals to the system keytab.
+***********************************************************************/
+
+int ads_keytab_create_default(ADS_STRUCT *ads)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ krb5_kvno kvno;
+ int i, found = 0;
+ char **oldEntries = NULL;
+
+ ret = ads_keytab_add_entry(ads, "host");
+ if (ret) {
+ DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'host'.\n"));
+ return ret;
+ }
+ ret = ads_keytab_add_entry(ads, "cifs");
+ if (ret) {
+ DEBUG(1,("ads_keytab_create_default: ads_keytab_add_entry failed while adding 'cifs'.\n"));
+ return ret;
+ }
+
+ kvno = (krb5_kvno) ads_get_kvno(ads, global_myname());
+ if (kvno == -1) {
+ DEBUG(1,("ads_keytab_create_default: ads_get_kvno failed to determine the system's kvno.\n"));
+ return -1;
+ }
+
+ DEBUG(3,("ads_keytab_create_default: Searching for keytab entries to preserve and update.\n"));
+ /* Now loop through the keytab and update any other existing entries... */
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ initialize_krb5_error_table();
+ ret = krb5_init_context(&context);
+ if (ret) {
+ DEBUG(1,("ads_keytab_create_default: could not krb5_init_context: %s\n",error_message(ret)));
+ return ret;
+ }
+ ret = krb5_kt_default(context, &keytab);
+ if (ret) {
+ DEBUG(1,("ads_keytab_create_default: krb5_kt_default failed (%s)\n",error_message(ret)));
+ goto done;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret != KRB5_KT_END && ret != ENOENT ) {
+ while ((ret = krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) == 0) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ found++;
+ }
+ }
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+
+ /*
+ * Hmmm. There is no "rewind" function for the keytab. This means we have a race condition
+ * where someone else could add entries after we've counted them. Re-open asap to minimise
+ * the race. JRA.
+ */
+
+ DEBUG(3, ("ads_keytab_create_default: Found %d entries in the keytab.\n", found));
+ if (!found) {
+ goto done;
+ }
+ oldEntries = (char **) malloc(found * sizeof(char *));
+ if (!oldEntries) {
+ DEBUG(1,("ads_keytab_create_default: Failed to allocate space to store the old keytab entries (malloc failed?).\n"));
+ ret = -1;
+ goto done;
+ }
+ memset(oldEntries, '\0', found * sizeof(char *));
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret != KRB5_KT_END && ret != ENOENT ) {
+ while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
+ if (kt_entry.vno != kvno) {
+ char *ktprinc = NULL;
+ char *p;
+
+ /* This returns a malloc'ed string in ktprinc. */
+ ret = krb5_unparse_name(context, kt_entry.principal, &ktprinc);
+ if (ret) {
+ DEBUG(1,("krb5_unparse_name failed (%s)\n", error_message(ret)));
+ goto done;
+ }
+ /*
+ * From looking at the krb5 source they don't seem to take locale
+ * or mb strings into account. Maybe this is because they assume utf8 ?
+ * In this case we may need to convert from utf8 to mb charset here ? JRA.
+ */
+ p = strchr_m(ktprinc, '/');
+ if (p) {
+ *p = '\0';
+ }
+ for (i = 0; i < found; i++) {
+ if (!oldEntries[i]) {
+ oldEntries[i] = ktprinc;
+ break;
+ }
+ if (!strcmp(oldEntries[i], ktprinc)) {
+ krb5_free_unparsed_name(context, ktprinc);
+ break;
+ }
+ }
+ if (i == found) {
+ krb5_free_unparsed_name(context, ktprinc);
+ }
+ }
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ }
+ ret = 0;
+ for (i = 0; oldEntries[i]; i++) {
+ ret |= ads_keytab_add_entry(ads, oldEntries[i]);
+ krb5_free_unparsed_name(context, oldEntries[i]);
+ }
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ ZERO_STRUCT(cursor);
+
+done:
+
+ SAFE_FREE(oldEntries);
+
+ {
+ krb5_keytab_entry zero_kt_entry;
+ ZERO_STRUCT(zero_kt_entry);
+ if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ }
+ }
+ {
+ krb5_kt_cursor zero_csr;
+ ZERO_STRUCT(zero_csr);
+ if ((memcmp(&cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return ret;
+}
+#endif /* HAVE_KRB5 */
diff --git a/source/libads/kerberos_verify.c b/source/libads/kerberos_verify.c
index 47559c1abb7..bdac22a9022 100644
--- a/source/libads/kerberos_verify.c
+++ b/source/libads/kerberos_verify.c
@@ -26,10 +26,182 @@
#ifdef HAVE_KRB5
-/*
- verify an incoming ticket and parse out the principal name and
- authorization_data if available
-*/
+/**********************************************************************************
+ Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so
+ it's more like what microsoft does... see comment in utils/net_ads.c in the
+ ads_keytab_add_entry function for details.
+***********************************************************************************/
+
+static BOOL ads_keytab_verify_ticket(krb5_context context, krb5_auth_context auth_context,
+ const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt)
+{
+ krb5_error_code ret = 0;
+ BOOL auth_ok = False;
+
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ char *princ_name = NULL;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = krb5_kt_default(context, &keytab);
+ if (ret) {
+ DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ while (!krb5_kt_next_entry(context, keytab, &kt_entry, &cursor)) {
+ ret = krb5_unparse_name(context, kt_entry.principal, &princ_name);
+ if (ret) {
+ DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ /* Look for a CIFS ticket */
+ if (!StrnCaseCmp(princ_name, "cifs/", 5)) {
+#ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK
+ krb5_auth_con_setuseruserkey(context, auth_context, &kt_entry.keyblock);
+#else
+ krb5_auth_con_setuseruserkey(context, auth_context, &kt_entry.key);
+#endif
+
+ p_packet->length = ticket->length;
+ p_packet->data = (krb5_pointer)ticket->data;
+
+ if (!(ret = krb5_rd_req(context, &auth_context, p_packet, NULL, NULL, NULL, pp_tkt))) {
+ unsigned int keytype;
+ krb5_free_unparsed_name(context, princ_name);
+ princ_name = NULL;
+#ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK
+ keytype = (unsigned int) kt_entry.keyblock.keytype;
+#else
+ keytype = (unsigned int) kt_entry.key.enctype;
+#endif
+ DEBUG(10,("ads_keytab_verify_ticket: enc type [%u] decrypted message !\n",
+ keytype));
+ auth_ok = True;
+ break;
+ }
+ }
+ krb5_free_unparsed_name(context, princ_name);
+ princ_name = NULL;
+ }
+ if (ret && ret != KRB5_KT_END) {
+ /* This failed because something went wrong, not because the keytab file was empty. */
+ DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_next_entry failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ out:
+
+ if (princ_name) {
+ krb5_free_unparsed_name(context, princ_name);
+ }
+ {
+ krb5_kt_cursor zero_csr;
+ ZERO_STRUCT(zero_csr);
+ if ((memcmp(&cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+
+ return auth_ok;
+}
+
+/**********************************************************************************
+ Try to verify a ticket using the secrets.tdb.
+***********************************************************************************/
+
+static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context,
+ krb5_principal host_princ,
+ const DATA_BLOB *ticket, krb5_data *p_packet, krb5_ticket **pp_tkt)
+{
+ krb5_error_code ret = 0;
+ BOOL auth_ok = False;
+ char *password_s = NULL;
+ krb5_data password;
+ krb5_enctype *enctypes = NULL;
+ int i;
+
+ if (!secrets_init()) {
+ DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n"));
+ return False;
+ }
+
+ password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+ if (!password_s) {
+ DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n"));
+ return False;
+ }
+
+ password.data = password_s;
+ password.length = strlen(password_s);
+
+ /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */
+
+ if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) {
+ DEBUG(1,("ads_secrets_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+
+ p_packet->length = ticket->length;
+ p_packet->data = (krb5_pointer)ticket->data;
+
+ /* We need to setup a auth context with each possible encoding type in turn. */
+ for (i=0;enctypes[i];i++) {
+ krb5_keyblock *key = NULL;
+
+ if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) {
+ goto out;
+ }
+
+ if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) {
+ SAFE_FREE(key);
+ continue;
+ }
+
+ krb5_auth_con_setuseruserkey(context, auth_context, key);
+
+ krb5_free_keyblock(context, key);
+
+ if (!(ret = krb5_rd_req(context, &auth_context, p_packet,
+ NULL,
+ NULL, NULL, pp_tkt))) {
+ DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n",
+ (unsigned int)enctypes[i] ));
+ auth_ok = True;
+ break;
+ }
+
+ DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10,
+ ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n",
+ (unsigned int)enctypes[i], error_message(ret)));
+ }
+
+ out:
+
+ free_kerberos_etypes(context, enctypes);
+ SAFE_FREE(password_s);
+
+ return auth_ok;
+}
+
+/**********************************************************************************
+ Verify an incoming ticket and parse out the principal name and
+ authorization_data if available.
+***********************************************************************************/
+
NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
char **principal, DATA_BLOB *auth_data,
DATA_BLOB *ap_rep,
@@ -41,43 +213,21 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
krb5_data packet;
krb5_ticket *tkt = NULL;
krb5_rcache rcache = NULL;
- int ret, i;
- krb5_keyblock *key = NULL;
+ int ret;
- krb5_principal host_princ;
+ krb5_principal host_princ = NULL;
char *host_princ_s = NULL;
- BOOL free_host_princ = False;
BOOL got_replay_mutex = False;
fstring myname;
- char *password_s = NULL;
- krb5_data password;
- krb5_enctype *enctypes = NULL;
-#if 0
- krb5_address local_addr;
- krb5_address remote_addr;
-#endif
BOOL auth_ok = False;
ZERO_STRUCT(packet);
- ZERO_STRUCT(password);
ZERO_STRUCTP(auth_data);
ZERO_STRUCTP(ap_rep);
+ ZERO_STRUCTP(session_key);
- if (!secrets_init()) {
- DEBUG(1,("ads_verify_ticket: secrets_init failed\n"));
- return NT_STATUS_LOGON_FAILURE;
- }
-
- password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
- if (!password_s) {
- DEBUG(1,("ads_verify_ticket: failed to fetch machine password\n"));
- return NT_STATUS_LOGON_FAILURE;
- }
-
- password.data = password_s;
- password.length = strlen(password_s);
-
+ initialize_krb5_error_table();
ret = krb5_init_context(&context);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret)));
@@ -87,7 +237,6 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
ret = krb5_set_default_realm(context, realm);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
@@ -98,22 +247,29 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
ret = krb5_auth_con_init(context, &auth_context);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
- fstrcpy(myname, global_myname());
+ name_to_fqdn(myname, global_myname());
strlower_m(myname);
- asprintf(&host_princ_s, "HOST/%s@%s", myname, lp_realm());
+ asprintf(&host_princ_s, "host/%s@%s", myname, lp_realm());
ret = krb5_parse_name(context, host_princ_s, &host_princ);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_parse_name(%s) failed (%s)\n",
host_princ_s, error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
- free_host_princ = True;
+
+ /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5
+ * code surrounding the replay cache... */
+
+ if (!grab_server_mutex("replay cache mutex")) {
+ DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
+ goto out;
+ }
+
+ got_replay_mutex = True;
/*
* JRA. We must set the rcache here. This will prevent replay attacks.
@@ -122,67 +278,21 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
ret = krb5_auth_con_setrcache(context, auth_context, rcache);
if (ret) {
DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
- /* CIFS doesn't use addresses in tickets. This would breat NAT. JRA */
-
- if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) {
- DEBUG(1,("ads_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n",
- error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
- goto out;
+ if (lp_use_kerberos_keytab()) {
+ auth_ok = ads_keytab_verify_ticket(context, auth_context, ticket, &packet, &tkt);
}
-
- /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5
- * code surrounding the replay cache... */
-
- if (!grab_server_mutex("replay cache mutex")) {
- DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n"));
- sret = NT_STATUS_LOGON_FAILURE;
- goto out;
- }
-
- got_replay_mutex = True;
-
- /* We need to setup a auth context with each possible encoding type in turn. */
- for (i=0;enctypes[i];i++) {
- if (!(key = (krb5_keyblock *)malloc(sizeof(*key)))) {
- sret = NT_STATUS_NO_MEMORY;
- goto out;
- }
-
- if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) {
- continue;
- }
-
- krb5_auth_con_setuseruserkey(context, auth_context, key);
-
- krb5_free_keyblock(context, key);
-
- packet.length = ticket->length;
- packet.data = (krb5_pointer)ticket->data;
-
- if (!(ret = krb5_rd_req(context, &auth_context, &packet,
- NULL,
- NULL, NULL, &tkt))) {
- DEBUG(10,("ads_verify_ticket: enc type [%u] decrypted message !\n",
- (unsigned int)enctypes[i] ));
- auth_ok = True;
- break;
- }
-
- DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10,
- ("ads_verify_ticket: enc type [%u] failed to decrypt with error %s\n",
- (unsigned int)enctypes[i], error_message(ret)));
+ if (!auth_ok) {
+ auth_ok = ads_secrets_verify_ticket(context, auth_context, host_princ,
+ ticket, &packet, &tkt);
}
release_server_mutex();
@@ -191,7 +301,6 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
if (!auth_ok) {
DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n",
error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
@@ -199,12 +308,12 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
if (ret) {
DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n",
error_message(ret)));
- sret = NT_STATUS_LOGON_FAILURE;
goto out;
}
*ap_rep = data_blob(packet.data, packet.length);
- free(packet.data);
+ SAFE_FREE(packet.data);
+ packet.length = 0;
get_krb5_smb_session_key(context, auth_context, session_key, True);
dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length);
@@ -241,29 +350,35 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket,
out:
- if (got_replay_mutex)
+ if (got_replay_mutex) {
release_server_mutex();
+ }
- if (!NT_STATUS_IS_OK(sret))
+ if (!NT_STATUS_IS_OK(sret)) {
data_blob_free(auth_data);
+ }
- if (!NT_STATUS_IS_OK(sret))
+ if (!NT_STATUS_IS_OK(sret)) {
data_blob_free(ap_rep);
+ }
- if (free_host_princ)
+ if (host_princ) {
krb5_free_principal(context, host_princ);
+ }
- if (tkt != NULL)
+ if (tkt != NULL) {
krb5_free_ticket(context, tkt);
- free_kerberos_etypes(context, enctypes);
- SAFE_FREE(password_s);
+ }
+
SAFE_FREE(host_princ_s);
- if (auth_context)
+ if (auth_context) {
krb5_auth_con_free(context, auth_context);
+ }
- if (context)
+ if (context) {
krb5_free_context(context);
+ }
return sret;
}
diff --git a/source/libads/krb5_setpw.c b/source/libads/krb5_setpw.c
index 5c859f0e995..84595212e6c 100644
--- a/source/libads/krb5_setpw.c
+++ b/source/libads/krb5_setpw.c
@@ -56,7 +56,7 @@ static DATA_BLOB encode_krb5_setpw(const char *principal, const char *password)
princ = strdup(principal);
- if ((c = strchr(princ, '/')) == NULL) {
+ if ((c = strchr_m(princ, '/')) == NULL) {
c = princ;
} else {
*c = '\0';
@@ -66,7 +66,7 @@ static DATA_BLOB encode_krb5_setpw(const char *principal, const char *password)
princ_part2 = c;
- if ((c = strchr(c, '@')) != NULL) {
+ if ((c = strchr_m(c, '@')) != NULL) {
*c = '\0';
c++;
realm = c;
@@ -462,14 +462,21 @@ ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
{
ADS_STATUS aret;
- krb5_error_code ret;
+ krb5_error_code ret = 0;
krb5_context context = NULL;
- krb5_principal principal;
- char *princ_name;
- char *realm;
- krb5_creds creds, *credsp;
+ krb5_principal principal = NULL;
+ char *princ_name = NULL;
+ char *realm = NULL;
+ krb5_creds creds, *credsp = NULL;
+#if KRB5_PRINC_REALM_RETURNS_REALM
+ krb5_realm orig_realm;
+#else
+ krb5_data orig_realm;
+#endif
krb5_ccache ccache = NULL;
+ ZERO_STRUCT(creds);
+
ret = krb5_init_context(&context);
if (ret) {
DEBUG(1,("Failed to init krb5 context (%s)\n", error_message(ret)));
@@ -487,14 +494,19 @@ ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
return ADS_ERROR_KRB5(ret);
}
- ZERO_STRUCT(creds);
-
- realm = strchr(princ, '@');
+ realm = strchr_m(princ, '@');
+ if (!realm) {
+ krb5_cc_close(context, ccache);
+ krb5_free_context(context);
+ DEBUG(1,("Failed to get realm\n"));
+ return ADS_ERROR_KRB5(-1);
+ }
realm++;
asprintf(&princ_name, "kadmin/changepw@%s", realm);
ret = krb5_parse_name(context, princ_name, &creds.server);
if (ret) {
+ krb5_cc_close(context, ccache);
krb5_free_context(context);
DEBUG(1,("Failed to parse kadmin/changepw (%s)\n", error_message(ret)));
return ADS_ERROR_KRB5(ret);
@@ -504,16 +516,23 @@ ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
/* parse the principal we got as a function argument */
ret = krb5_parse_name(context, princ, &principal);
if (ret) {
+ krb5_cc_close(context, ccache);
+ krb5_free_principal(context, creds.server);
krb5_free_context(context);
DEBUG(1,("Failed to parse %s (%s)\n", princ_name, error_message(ret)));
return ADS_ERROR_KRB5(ret);
}
- krb5_princ_set_realm(context, creds.server,
- krb5_princ_realm(context, principal));
+ /* The creds.server principal takes ownership of this memory.
+ Remember to set back to original value before freeing. */
+ orig_realm = *krb5_princ_realm(context, creds.server);
+ krb5_princ_set_realm(context, creds.server, krb5_princ_realm(context, principal));
ret = krb5_cc_get_principal(context, ccache, &creds.client);
if (ret) {
+ krb5_cc_close(context, ccache);
+ krb5_princ_set_realm(context, creds.server, &orig_realm);
+ krb5_free_principal(context, creds.server);
krb5_free_principal(context, principal);
krb5_free_context(context);
DEBUG(1,("Failed to get principal from ccache (%s)\n",
@@ -523,7 +542,10 @@ ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
if (ret) {
+ krb5_cc_close(context, ccache);
krb5_free_principal(context, creds.client);
+ krb5_princ_set_realm(context, creds.server, &orig_realm);
+ krb5_free_principal(context, creds.server);
krb5_free_principal(context, principal);
krb5_free_context(context);
DEBUG(1,("krb5_get_credentials failed (%s)\n", error_message(ret)));
@@ -538,7 +560,10 @@ ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
krb5_free_creds(context, credsp);
krb5_free_principal(context, creds.client);
+ krb5_princ_set_realm(context, creds.server, &orig_realm);
+ krb5_free_principal(context, creds.server);
krb5_free_principal(context, principal);
+ krb5_cc_close(context, ccache);
krb5_free_context(context);
return aret;
diff --git a/source/libads/ldap.c b/source/libads/ldap.c
index e018eeb2da9..78ea9f1497d 100644
--- a/source/libads/ldap.c
+++ b/source/libads/ldap.c
@@ -37,6 +37,35 @@
* codepoints in UTF-8). This may have to change at some point
**/
+static SIG_ATOMIC_T gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(void)
+{
+ gotalarm = 1;
+}
+
+ LDAP *ldap_open_with_timeout(const char *server, int port, unsigned int to)
+{
+ LDAP *ldp = NULL;
+
+ /* Setup timeout */
+ gotalarm = 0;
+ CatchSignal(SIGALRM, SIGNAL_CAST gotalarm_sig);
+ alarm(to);
+ /* End setup timeout. */
+
+ ldp = ldap_open(server, port);
+
+ /* Teardown timeout. */
+ CatchSignal(SIGALRM, SIGNAL_CAST SIG_IGN);
+ alarm(0);
+
+ return ldp;
+}
/*
try a connection to a given ldap server, returning True and setting the servers IP
@@ -58,7 +87,7 @@ static BOOL ads_try_connect(ADS_STRUCT *ads, const char *server, unsigned port)
/* this copes with inet_ntoa brokenness */
srv = strdup(server);
- ads->ld = ldap_open(srv, port);
+ ads->ld = ldap_open_with_timeout(srv, port, lp_ldap_timeout());
if (!ads->ld) {
free(srv);
return False;
@@ -226,11 +255,10 @@ got_connection:
ldap_set_option(ads->ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (!ads->auth.user_name) {
- /* by default use the machine account */
- fstring myname;
- fstrcpy(myname, global_myname());
- strlower_m(myname);
- asprintf(&ads->auth.user_name, "HOST/%s", myname);
+ /* have to use the userPrincipalName value here and
+ not servicePrincipalName; found by Guenther Deschner @ Sernet */
+
+ asprintf(&ads->auth.user_name, "host/%s", global_myname() );
}
if (!ads->auth.realm) {
@@ -730,7 +758,7 @@ char *ads_get_dn(ADS_STRUCT *ads, void *msg)
* @param host Hostname to search for
* @return status of search
**/
-ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
+ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *machine)
{
ADS_STATUS status;
char *expr;
@@ -738,13 +766,13 @@ ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, void **res, const char *host)
/* the easiest way to find a machine account anywhere in the tree
is to look for hostname$ */
- if (asprintf(&expr, "(samAccountName=%s$)", host) == -1) {
+ if (asprintf(&expr, "(samAccountName=%s$)", machine) == -1) {
DEBUG(1, ("asprintf failed!\n"));
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
status = ads_search(ads, res, expr, attrs);
- free(expr);
+ SAFE_FREE(expr);
return status;
}
@@ -979,18 +1007,251 @@ char *ads_ou_string(const char *org_unit)
return ads_build_path(org_unit, "\\/", "ou=", 1);
}
+/**
+ * Adds (appends) an item to an attribute array, rather then
+ * replacing the whole list
+ * @param ctx An initialized TALLOC_CTX
+ * @param mods An initialized ADS_MODLIST
+ * @param name name of the ldap attribute to append to
+ * @param vals an array of values to add
+ * @return status of addition
+ **/
+ADS_STATUS ads_add_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char **vals)
+{
+ return ads_modlist_add(ctx, mods, LDAP_MOD_ADD, name, (const void **) vals);
+}
-/*
- add a machine account to the ADS server
-*/
-static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname,
+/**
+ * Determines the computer account's current KVNO via an LDAP lookup
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account.
+ * @return the kvno for the computer account, or -1 in case of a failure.
+ **/
+
+uint32 ads_get_kvno(ADS_STRUCT *ads, const char *machine_name)
+{
+ LDAPMessage *res = NULL;
+ uint32 kvno = (uint32)-1; /* -1 indicates a failure */
+ char *filter;
+ const char *attrs[] = {"msDS-KeyVersionNumber", NULL};
+ char *dn_string = NULL;
+ ADS_STATUS ret = ADS_ERROR(LDAP_SUCCESS);
+
+ DEBUG(5,("ads_get_kvno: Searching for host %s\n", machine_name));
+ if (asprintf(&filter, "(samAccountName=%s$)", machine_name) == -1) {
+ return kvno;
+ }
+ ret = ads_search(ads, (void**) &res, filter, attrs);
+ SAFE_FREE(filter);
+ if (!ADS_ERR_OK(ret) && ads_count_replies(ads, res)) {
+ DEBUG(1,("ads_get_kvno: Computer Account For %s not found.\n", machine_name));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+
+ dn_string = ads_get_dn(ads, res);
+ if (!dn_string) {
+ DEBUG(0,("ads_get_kvno: out of memory.\n"));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+ DEBUG(5,("ads_get_kvno: Using: %s\n", dn_string));
+ ads_memfree(ads, dn_string);
+
+ /* ---------------------------------------------------------
+ * 0 is returned as a default KVNO from this point on...
+ * This is done because Windows 2000 does not support key
+ * version numbers. Chances are that a failure in the next
+ * step is simply due to Windows 2000 being used for a
+ * domain controller. */
+ kvno = 0;
+
+ if (!ads_pull_uint32(ads, res, "msDS-KeyVersionNumber", &kvno)) {
+ DEBUG(3,("ads_get_kvno: Error Determining KVNO!\n"));
+ DEBUG(3,("ads_get_kvno: Windows 2000 does not support KVNO's, so this may be normal.\n"));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+
+ /* Success */
+ DEBUG(5,("ads_get_kvno: Looked Up KVNO of: %d\n", kvno));
+ ads_msgfree(ads, res);
+ return kvno;
+}
+
+/**
+ * This clears out all registered spn's for a given hostname
+ * @param ads An initilaized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer.
+ * @return 0 upon success, non-zero otherwise.
+ **/
+
+ADS_STATUS ads_clear_service_principal_names(ADS_STRUCT *ads, const char *machine_name)
+{
+ TALLOC_CTX *ctx;
+ LDAPMessage *res = NULL;
+ ADS_MODLIST mods;
+ const char *servicePrincipalName[1] = {NULL};
+ ADS_STATUS ret = ADS_ERROR(LDAP_SUCCESS);
+ char *dn_string = NULL;
+
+ ret = ads_find_machine_acct(ads, (void **)&res, machine_name);
+ if (!ADS_ERR_OK(ret) || ads_count_replies(ads, res) != 1) {
+ DEBUG(5,("ads_clear_service_principal_names: WARNING: Host Account for %s not found... skipping operation.\n", machine_name));
+ DEBUG(5,("ads_clear_service_principal_names: WARNING: Service Principals for %s have NOT been cleared.\n", machine_name));
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+
+ DEBUG(5,("ads_clear_service_principal_names: Host account for %s found\n", machine_name));
+ ctx = talloc_init("ads_clear_service_principal_names");
+ if (!ctx) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (!(mods = ads_init_mods(ctx))) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_clear_service_principal_names: Error creating strlist.\n"));
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+ }
+ dn_string = ads_get_dn(ads, res);
+ if (!dn_string) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_gen_mod(ads, dn_string, mods);
+ ads_memfree(ads,dn_string);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_clear_service_principal_names: Error: Updating Service Principals for machine %s in LDAP\n",
+ machine_name));
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+ }
+
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+}
+
+/**
+ * This adds a service principal name to an existing computer account
+ * (found by hostname) in AD.
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account.
+ * @param spn A string of the service principal to add, i.e. 'host'
+ * @return 0 upon sucess, or non-zero if a failure occurs
+ **/
+
+ADS_STATUS ads_add_service_principal_name(ADS_STRUCT *ads, const char *machine_name, const char *spn)
+{
+ ADS_STATUS ret;
+ TALLOC_CTX *ctx;
+ LDAPMessage *res = NULL;
+ char *host_spn, *host_upn, *psp1, *psp2;
+ ADS_MODLIST mods;
+ fstring my_fqdn;
+ char *dn_string = NULL;
+ const char *servicePrincipalName[3] = {NULL, NULL, NULL};
+
+ ret = ads_find_machine_acct(ads, (void **)&res, machine_name);
+ if (!ADS_ERR_OK(ret) || ads_count_replies(ads, res) != 1) {
+ DEBUG(1,("ads_add_service_principal_name: WARNING: Host Account for %s not found... skipping operation.\n",
+ machine_name));
+ DEBUG(1,("ads_add_service_principal_name: WARNING: Service Principal '%s/%s@%s' has NOT been added.\n",
+ spn, machine_name, ads->config.realm));
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+
+ DEBUG(1,("ads_add_service_principal_name: Host account for %s found\n", machine_name));
+ if (!(ctx = talloc_init("ads_add_service_principal_name"))) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ name_to_fqdn(my_fqdn, machine_name);
+ if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", my_fqdn))) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+ if (!(host_upn = talloc_asprintf(ctx, "%s@%s", host_spn, ads->config.realm))) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+
+ /* Add the extra principal */
+ psp1 = talloc_asprintf(ctx, "%s/%s", spn, machine_name);
+ strupper_m(psp1);
+ strlower_m(&psp1[strlen(spn)]);
+ DEBUG(5,("ads_add_service_principal_name: INFO: Adding %s to host %s\n", psp1, machine_name));
+ servicePrincipalName[0] = psp1;
+ psp2 = talloc_asprintf(ctx, "%s/%s.%s", spn, machine_name, ads->config.realm);
+ strupper_m(psp2);
+ strlower_m(&psp2[strlen(spn)]);
+ DEBUG(5,("ads_add_service_principal_name: INFO: Adding %s to host %s\n", psp2, machine_name));
+ servicePrincipalName[1] = psp2;
+
+ if (!(mods = ads_init_mods(ctx))) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_add_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n"));
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ret;
+ }
+ dn_string = ads_get_dn(ads, res);
+ if (!dn_string) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_gen_mod(ads, dn_string, mods);
+ ads_memfree(ads,dn_string);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n"));
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ret;
+ }
+
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ret;
+}
+
+/**
+ * adds a machine account to the ADS server
+ * @param ads An intialized ADS_STRUCT
+ * @param machine_name - the NetBIOS machine name of this account.
+ * @param account_type A number indicating the type of account to create
+ * @param org_unit The LDAP path in which to place this account
+ * @return 0 upon success, or non-zero otherwise
+**/
+
+static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *machine_name,
uint32 account_type,
const char *org_unit)
{
ADS_STATUS ret, status;
char *host_spn, *host_upn, *new_dn, *samAccountName, *controlstr;
- char *ou_str;
TALLOC_CTX *ctx;
ADS_MODLIST mods;
const char *objectClass[] = {"top", "person", "organizationalPerson",
@@ -999,87 +1260,106 @@ static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname,
char *psp, *psp2;
unsigned acct_control;
unsigned exists=0;
- LDAPMessage *res;
+ fstring my_fqdn;
+ LDAPMessage *res = NULL;
- status = ads_find_machine_acct(ads, (void **)&res, hostname);
+ if (!(ctx = talloc_init("ads_add_machine_acct")))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+
+ name_to_fqdn(my_fqdn, machine_name);
+
+ status = ads_find_machine_acct(ads, (void **)&res, machine_name);
if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) {
- DEBUG(0, ("Host account for %s already exists - modifying old account\n", hostname));
+ char *dn_string = ads_get_dn(ads, res);
+ if (!dn_string) {
+ 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,dn_string);
+ DEBUG(0, ("ads_add_machine_acct: Host account for %s already exists - modifying old account\n",
+ machine_name));
exists=1;
- }
+ } else {
+ char *ou_str = ads_ou_string(org_unit);
+ if (!ou_str) {
+ DEBUG(1, ("ads_add_machine_acct: ads_ou_string returned NULL (malloc failure?)\n"));
+ goto done;
+ }
+ new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", machine_name, ou_str,
+ ads->config.bind_path);
- if (!(ctx = talloc_init("machine_account")))
- return ADS_ERROR(LDAP_NO_MEMORY);
+ SAFE_FREE(ou_str);
+ }
- ret = ADS_ERROR(LDAP_NO_MEMORY);
+ if (!new_dn) {
+ goto done;
+ }
- if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", hostname)))
+ if (!(host_spn = talloc_asprintf(ctx, "HOST/%s", machine_name)))
goto done;
if (!(host_upn = talloc_asprintf(ctx, "%s@%s", host_spn, ads->config.realm)))
goto done;
- ou_str = ads_ou_string(org_unit);
- if (!ou_str) {
- DEBUG(1, ("ads_ou_string returned NULL (malloc failure?)\n"));
- goto done;
- }
- new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", hostname, ou_str,
- ads->config.bind_path);
- servicePrincipalName[0] = talloc_asprintf(ctx, "HOST/%s", hostname);
+ servicePrincipalName[0] = talloc_asprintf(ctx, "HOST/%s", machine_name);
psp = talloc_asprintf(ctx, "HOST/%s.%s",
- hostname,
- ads->config.realm);
+ machine_name,
+ ads->config.realm);
strlower_m(&psp[5]);
servicePrincipalName[1] = psp;
- servicePrincipalName[2] = talloc_asprintf(ctx, "CIFS/%s", hostname);
+ servicePrincipalName[2] = talloc_asprintf(ctx, "CIFS/%s", machine_name);
psp2 = talloc_asprintf(ctx, "CIFS/%s.%s",
- hostname,
+ machine_name,
ads->config.realm);
strlower_m(&psp2[5]);
servicePrincipalName[3] = psp2;
- free(ou_str);
- if (!new_dn)
- goto done;
-
- if (!(samAccountName = talloc_asprintf(ctx, "%s$", hostname)))
+ if (!(samAccountName = talloc_asprintf(ctx, "%s$", machine_name))) {
goto done;
+ }
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)))
+ if (!(controlstr = talloc_asprintf(ctx, "%u", acct_control))) {
goto done;
+ }
- if (!(mods = ads_init_mods(ctx)))
+ if (!(mods = ads_init_mods(ctx))) {
goto done;
+ }
if (!exists) {
- ads_mod_str(ctx, &mods, "cn", hostname);
+ ads_mod_str(ctx, &mods, "cn", machine_name);
ads_mod_str(ctx, &mods, "sAMAccountName", samAccountName);
ads_mod_str(ctx, &mods, "userAccountControl", controlstr);
ads_mod_strlist(ctx, &mods, "objectClass", objectClass);
}
- ads_mod_str(ctx, &mods, "dNSHostName", hostname);
+ ads_mod_str(ctx, &mods, "dNSHostName", my_fqdn);
ads_mod_str(ctx, &mods, "userPrincipalName", host_upn);
ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
ads_mod_str(ctx, &mods, "operatingSystem", "Samba");
ads_mod_str(ctx, &mods, "operatingSystemVersion", SAMBA_VERSION_STRING);
- if (!exists)
+ if (!exists) {
ret = ads_gen_add(ads, new_dn, mods);
- else
+ } else {
ret = ads_gen_mod(ads, new_dn, mods);
+ }
- if (!ADS_ERR_OK(ret))
+ if (!ADS_ERR_OK(ret)) {
goto done;
+ }
/* Do not fail if we can't set security descriptor
* it shouldn't be mandatory and probably we just
* don't have enough rights to do it.
*/
if (!exists) {
- status = ads_set_machine_sd(ads, hostname, new_dn);
+ status = ads_set_machine_sd(ads, machine_name, new_dn);
if (!ADS_ERR_OK(status)) {
DEBUG(0, ("Warning: ads_set_machine_sd: %s\n",
@@ -1087,6 +1367,7 @@ static ADS_STATUS ads_add_machine_acct(ADS_STRUCT *ads, const char *hostname,
}
}
done:
+ ads_msgfree(ads, res);
talloc_destroy(ctx);
return ret;
}
@@ -1303,47 +1584,50 @@ int ads_count_replies(ADS_STRUCT *ads, void *res)
* Join a machine to a realm
* Creates the machine account and sets the machine password
* @param ads connection to ads server
- * @param hostname name of host to add
+ * @param machine name of host to add
* @param org_unit Organizational unit to place machine in
* @return status of join
**/
-ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *hostname,
+ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *machine_name,
uint32 account_type, const char *org_unit)
{
ADS_STATUS status;
- LDAPMessage *res;
- char *host;
+ LDAPMessage *res = NULL;
+ char *machine;
- /* hostname must be lowercase */
- host = strdup(hostname);
- strlower_m(host);
+ /* machine name must be lowercase */
+ machine = strdup(machine_name);
+ strlower_m(machine);
/*
- status = ads_find_machine_acct(ads, (void **)&res, host);
+ status = ads_find_machine_acct(ads, (void **)&res, machine);
if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) {
- DEBUG(0, ("Host account for %s already exists - deleting old account\n", host));
- status = ads_leave_realm(ads, host);
+ DEBUG(0, ("Host account for %s already exists - deleting old account\n", machine));
+ status = ads_leave_realm(ads, machine);
if (!ADS_ERR_OK(status)) {
DEBUG(0, ("Failed to delete host '%s' from the '%s' realm.\n",
- host, ads->config.realm));
+ machine, ads->config.realm));
return status;
}
}
*/
- status = ads_add_machine_acct(ads, host, account_type, org_unit);
+ status = ads_add_machine_acct(ads, machine, account_type, org_unit);
if (!ADS_ERR_OK(status)) {
- DEBUG(0, ("ads_add_machine_acct: %s\n", ads_errstr(status)));
+ DEBUG(0, ("ads_add_machine_acct (%s): %s\n", machine, ads_errstr(status)));
+ SAFE_FREE(machine);
return status;
}
- status = ads_find_machine_acct(ads, (void **)&res, host);
+ status = ads_find_machine_acct(ads, (void **)&res, machine);
if (!ADS_ERR_OK(status)) {
- DEBUG(0, ("Host account test failed\n"));
+ DEBUG(0, ("Host account test failed for machine %s\n", machine));
+ SAFE_FREE(machine);
return status;
}
- free(host);
+ SAFE_FREE(machine);
+ ads_msgfree(ads, res);
return status;
}
diff --git a/source/libads/sasl.c b/source/libads/sasl.c
index 18cbb465887..8eb2c86bed5 100644
--- a/source/libads/sasl.c
+++ b/source/libads/sasl.c
@@ -201,14 +201,14 @@ static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
/* make sure the server understands kerberos */
for (i=0;OIDs[i];i++) {
- DEBUG(3,("got OID=%s\n", OIDs[i]));
+ DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
strcmp(OIDs[i], OID_KERBEROS5) == 0) {
got_kerberos_mechanism = True;
}
free(OIDs[i]);
}
- DEBUG(3,("got principal=%s\n", principal));
+ DEBUG(3,("ads_sasl_spnego_bind: got server principal name =%s\n", principal));
#ifdef HAVE_KRB5
if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&