diff options
author | Gerald Carter <jerry@samba.org> | 2004-07-07 19:18:31 +0000 |
---|---|---|
committer | Gerald Carter <jerry@samba.org> | 2004-07-07 19:18:31 +0000 |
commit | 0cf550c32aa0018ad834fc2d3cd90d9b2b146621 (patch) | |
tree | 687da224b86274ebc9c46591a812d206935c8efd /source/libads | |
parent | 99c6d9bd34b106ae5109ffed4812b89ef0f3eaa8 (diff) | |
download | samba-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.c | 4 | ||||
-rw-r--r-- | source/libads/kerberos_keytab.c | 554 | ||||
-rw-r--r-- | source/libads/kerberos_verify.c | 313 | ||||
-rw-r--r-- | source/libads/krb5_setpw.c | 49 | ||||
-rw-r--r-- | source/libads/ldap.c | 410 | ||||
-rw-r--r-- | source/libads/sasl.c | 4 |
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) && |